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 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
553 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-t', table, '-nL'])
554 for p in os.listdir('/sys/class/net/'):
556 f = open('/sys/class/net/%s/type' % p, 'r')
559 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
561 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
562 if not p.startswith('vif') and not p.startswith('tap'):
563 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
564 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
565 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
566 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
568 cmd_output(CAP_NETWORK_STATUS,
569 [TC, '-s', '-d', 'class', 'show', 'dev', p])
572 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
573 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
574 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
575 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
576 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
577 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
579 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
581 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
582 vspid = int(vspidfile.readline().strip())
584 for b in bond_list(vspid):
585 cmd_output(CAP_NETWORK_STATUS,
586 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
587 'ovs-appctl-bond-show-%s.out' % b)
591 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
592 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
594 system_logs = ([ VAR_LOG_DIR + x for x in
595 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
596 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
597 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
598 ['ovs-vswitchd.log', 'ovsdb-server.log',
599 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
600 log_output(CAP_SYSTEM_LOGS, system_logs)
601 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
603 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
604 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
606 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
608 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
610 file_output(CAP_YUM, [YUM_LOG])
611 tree_output(CAP_YUM, YUM_REPOS_DIR)
612 cmd_output(CAP_YUM, [RPM, '-qa'])
613 file_output(CAP_YUM, [APT_SOURCES_LIST])
614 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
615 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
617 # Filter out ovs relevant information if --ovs option passed
618 # else collect all information
622 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
624 ovs_info_list = ['process-tree']
625 # We cannot use iteritems, since we modify 'data' as we pass through
626 for (k, v) in data.items():
632 if info not in ovs_info_list and cap not in ovs_info_caps:
636 filter = ",".join(filters)
641 load_plugins(filter=filter)
645 # permit the user to filter out data
646 # We cannot use iteritems, since we modify 'data' as we pass through
647 for (k, v) in sorted(data.items()):
653 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
656 # collect selected data now
657 output_ts('Running commands to collect data')
660 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
663 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
667 if output_file is None:
670 dirname = os.path.dirname(output_file)
671 if dirname and not os.path.exists(dirname):
678 output_ts('Creating output file')
680 if output_type.startswith('tar'):
681 make_tar(subdir, output_type, output_fd, output_file)
683 make_zip(subdir, output_file)
686 print >>sys.stderr, "Category sizes (max, actual):\n"
687 for c in caps.keys():
688 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
692 def dump_scsi_hosts(cap):
694 l = os.listdir('/sys/class/scsi_host')
700 f = open('/sys/class/scsi_host/%s/proc_name' % h)
701 procname = f.readline().strip("\n")
707 f = open('/sys/class/scsi_host/%s/model_name' % h)
708 modelname = f.readline().strip("\n")
714 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
718 def module_info(cap):
719 output = StringIO.StringIO()
720 modules = open(PROC_MODULES, 'r')
724 module = line.split()[0]
725 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
730 return output.getvalue()
733 def multipathd_topology(cap):
734 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
735 stdout=PIPE, stderr=dev_null)
736 stdout, stderr = pipe.communicate('show topology')
741 output = StringIO.StringIO()
742 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
746 if not procs[0].timed_out:
747 return output.getvalue().splitlines()
751 output = StringIO.StringIO()
752 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
756 if not procs[0].timed_out:
757 bonds = output.getvalue().splitlines()[1:]
758 return [x.split('\t')[1] for x in bonds]
764 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
766 fh = open('/proc/'+d+'/cmdline')
768 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
770 if not num_fds in fd_dict:
771 fd_dict[num_fds] = []
772 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
775 keys = fd_dict.keys()
776 keys.sort(lambda a, b: int(b) - int(a))
778 output += "%s: %s\n" % (k, str(fd_dict[k]))
781 def dump_rdac_groups(cap):
782 output = StringIO.StringIO()
783 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
787 if not procs[0].timed_out:
789 for line in output.getvalue().splitlines():
790 if line.startswith('ID'):
792 elif line.startswith('----'):
795 group, _ = line.split(None, 1)
796 cmd_output(cap, [MPPUTIL, '-g', group])
798 def load_plugins(just_capabilities=False, filter=None):
799 def getText(nodelist):
801 for node in nodelist:
802 if node.nodeType == node.TEXT_NODE:
806 def getBoolAttr(el, attr, default=False):
808 val = el.getAttribute(attr).lower()
809 if val in ['true', 'false', 'yes', 'no']:
810 ret = val in ['true', 'yes']
813 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
814 if not caps.has_key(dir):
815 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
817 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
818 assert xmldoc.documentElement.tagName == "capability"
820 pii, min_size, max_size, min_time, max_time, mime = \
821 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
823 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
824 pii = xmldoc.documentElement.getAttribute("pii")
825 if xmldoc.documentElement.getAttribute("min_size") != '':
826 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
827 if xmldoc.documentElement.getAttribute("max_size") != '':
828 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
829 if xmldoc.documentElement.getAttribute("min_time") != '':
830 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
831 if xmldoc.documentElement.getAttribute("max_time") != '':
832 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
833 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
834 mime = xmldoc.documentElement.getAttribute("mime")
835 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
836 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
838 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
840 if just_capabilities:
843 plugdir = os.path.join(PLUGIN_DIR, dir)
844 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
845 xmldoc = parse(os.path.join(plugdir, file))
846 assert xmldoc.documentElement.tagName == "collect"
848 for el in xmldoc.documentElement.getElementsByTagName("*"):
849 filters_tmp = el.getAttribute("filters")
850 if filters_tmp == '':
853 filters = filters_tmp.split(',')
854 if not(filter is None or filter in filters):
856 if el.tagName == "files":
857 newest_first = getBoolAttr(el, 'newest_first')
858 if el.getAttribute("type") == "logs":
859 log_output(dir, getText(el.childNodes).split(),
860 newest_first=newest_first)
862 file_output(dir, getText(el.childNodes).split(),
863 newest_first=newest_first)
864 elif el.tagName == "directory":
865 pattern = el.getAttribute("pattern")
866 if pattern == '': pattern = None
867 negate = getBoolAttr(el, 'negate')
868 newest_first = getBoolAttr(el, 'newest_first')
869 tree_output(dir, getText(el.childNodes),
870 pattern and re.compile(pattern) or None,
871 negate=negate, newest_first=newest_first)
872 elif el.tagName == "command":
873 label = el.getAttribute("label")
874 if label == '': label = None
875 binary = getBoolAttr(el, 'binary')
876 cmd_output(dir, getText(el.childNodes), label, binary=binary)
878 def make_tar(subdir, suffix, output_fd, output_file):
879 global SILENT_MODE, data
882 if suffix == 'tar.bz2':
884 elif suffix == 'tar.gz':
888 if output_file is None:
889 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
891 filename = output_file
892 old_umask = os.umask(0077)
893 tf = tarfile.open(filename, mode)
896 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
899 for (k, v) in data.items():
901 tar_filename = os.path.join(subdir, construct_filename(k, v))
902 ti = tarfile.TarInfo(tar_filename)
907 if v.has_key('output'):
908 ti.mtime = v['output'].mtime
909 ti.size = len(v['output'].getvalue())
911 tf.addfile(ti, v['output'])
912 elif v.has_key('filename'):
913 s = os.stat(v['filename'])
914 ti.mtime = s.st_mtime
916 tf.addfile(ti, file(v['filename']))
923 output ('Writing tarball %s successful.' % filename)
928 def make_zip(subdir, output_file):
929 global SILENT_MODE, data
931 if output_file is None:
932 filename = "%s/%s.zip" % (BUG_DIR, subdir)
934 filename = output_file
935 old_umask = os.umask(0077)
936 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
940 for (k, v) in data.items():
942 dest = os.path.join(subdir, construct_filename(k, v))
944 if v.has_key('output'):
945 zf.writestr(dest, v['output'].getvalue())
947 if os.stat(v['filename']).st_size < 50:
948 compress_type = zipfile.ZIP_STORED
950 compress_type = zipfile.ZIP_DEFLATED
951 zf.write(v['filename'], dest, compress_type)
957 output ('Writing archive %s successful.' % filename)
962 def make_inventory(inventory, subdir):
963 document = getDOMImplementation().createDocument(
964 None, INVENTORY_XML_ROOT, None)
966 # create summary entry
967 s = document.createElement(INVENTORY_XML_SUMMARY)
968 user = os.getenv('SUDO_USER', os.getenv('USER'))
970 s.setAttribute('user', user)
971 s.setAttribute('date', time.strftime('%c'))
972 s.setAttribute('hostname', platform.node())
973 s.setAttribute('uname', ' '.join(platform.uname()))
974 s.setAttribute('uptime', commands.getoutput(UPTIME))
975 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
977 map(lambda (k, v): inventory_entry(document, subdir, k, v),
979 return document.toprettyxml()
981 def inventory_entry(document, subdir, k, v):
983 el = document.createElement(INVENTORY_XML_ELEMENT)
984 el.setAttribute('capability', v['cap'])
985 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
986 el.setAttribute('md5sum', md5sum(v))
987 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
994 if d.has_key('filename'):
995 f = open(d['filename'])
1001 elif d.has_key('output'):
1002 m.update(d['output'].getvalue())
1003 return m.hexdigest()
1006 def construct_filename(k, v):
1007 if v.has_key('filename'):
1008 if v['filename'][0] == '/':
1009 return v['filename'][1:]
1011 return v['filename']
1012 s = k.replace(' ', '-')
1013 s = s.replace('--', '-')
1014 s = s.replace('/', '%')
1015 if s.find('.') == -1:
1020 def update_capabilities():
1023 def update_cap_size(cap, size):
1024 update_cap(cap, MIN_SIZE, size)
1025 update_cap(cap, MAX_SIZE, size)
1026 update_cap(cap, CHECKED, size > 0)
1029 def update_cap(cap, k, v):
1033 caps[cap] = tuple(l)
1036 def size_of_dir(d, pattern=None, negate=False):
1037 if os.path.isdir(d):
1038 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1044 def size_of_all(files, pattern=None, negate=False):
1045 return sum([size_of(f, pattern, negate) for f in files])
1048 def matches(f, pattern, negate):
1050 return not matches(f, pattern, False)
1052 return pattern is None or pattern.match(f)
1055 def size_of(f, pattern, negate):
1056 if os.path.isfile(f) and matches(f, pattern, negate):
1057 return os.stat(f)[6]
1059 return size_of_dir(f, pattern, negate)
1062 def print_capabilities():
1063 document = getDOMImplementation().createDocument(
1064 "ns", CAP_XML_ROOT, None)
1065 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1066 print document.toprettyxml()
1068 def capability(document, key):
1070 el = document.createElement(CAP_XML_ELEMENT)
1071 el.setAttribute('key', c[KEY])
1072 el.setAttribute('pii', c[PII])
1073 el.setAttribute('min-size', str(c[MIN_SIZE]))
1074 el.setAttribute('max-size', str(c[MAX_SIZE]))
1075 el.setAttribute('min-time', str(c[MIN_TIME]))
1076 el.setAttribute('max-time', str(c[MAX_TIME]))
1077 el.setAttribute('content-type', c[MIME])
1078 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1079 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1083 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1084 return '\n'.join([format % i for i in d.items()]) + '\n'
1088 yn = raw_input(prompt)
1090 return len(yn) == 0 or yn.lower()[0] == 'y'
1093 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1098 f = open('/proc/partitions')
1101 for line in f.readlines():
1102 (major, minor, blocks, name) = line.split()
1103 if int(major) < 254 and not partition_re.match(name):
1114 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1115 self.command = command
1116 self.max_time = max_time
1118 self.running = False
1120 self.timed_out = False
1122 self.timeout = int(time.time()) + self.max_time
1123 self.filter = filter
1124 self.filter_state = {}
1126 self.bufsize = 1048576 # 1MB buffer
1128 self.bufsize = 1 # line buffered
1134 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1137 self.timed_out = False
1139 if ProcOutput.debug:
1140 output_ts("Starting '%s'" % self.cmdAsStr())
1141 self.proc = Popen(self.command, bufsize=self.bufsize,
1142 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1143 shell=isinstance(self.command, str))
1144 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1145 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1149 output_ts("'%s' failed" % self.cmdAsStr())
1150 self.running = False
1153 def terminate(self):
1156 self.proc.stdout.close()
1157 os.kill(self.proc.pid, SIGTERM)
1161 self.running = False
1162 self.status = SIGTERM
1164 def read_line(self):
1166 if self.bufsize == 1:
1167 line = self.proc.stdout.readline()
1169 line = self.proc.stdout.read(self.bufsize)
1172 self.proc.stdout.close()
1173 self.status = self.proc.wait()
1175 self.running = False
1178 line = self.filter(line, self.filter_state)
1180 self.inst.write(line)
1182 def run_procs(procs):
1190 active_procs.append(p)
1191 pipes.append(p.proc.stdout)
1193 elif p.status == None and not p.failed and not p.timed_out:
1196 active_procs.append(p)
1197 pipes.append(p.proc.stdout)
1204 (i, o, x) = select(pipes, [], [], 1.0)
1205 now = int(time.time())
1207 # handle process output
1208 for p in active_procs:
1209 if p.proc.stdout in i:
1213 if p.running and now > p.timeout:
1214 output_ts("'%s' timed out" % p.cmdAsStr())
1216 p.inst.write("\n** timeout **\n")
1224 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1226 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1234 class StringIOmtime(StringIO.StringIO):
1235 def __init__(self, buf=''):
1236 StringIO.StringIO.__init__(self, buf)
1237 self.mtime = time.time()
1240 StringIO.StringIO.write(self, s)
1241 self.mtime = time.time()
1244 if __name__ == "__main__":
1247 except KeyboardInterrupt:
1248 print "\nInterrupted."