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'
162 # PII -- Personally identifiable information. Of particular concern are
163 # things that would identify customers, or their network topology.
164 # Passwords are never to be included in any bug report, regardless of any PII
167 # NO -- No PII will be in these entries.
168 # YES -- PII will likely or certainly be in these entries.
169 # MAYBE -- The user may wish to audit these entries for PII.
170 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
171 # but since we encourage customers to edit these files, PII may have been
172 # introduced by the customer. This is used in particular for the networking
179 PII_IF_CUSTOMIZED = 'if_customized'
190 MIME_DATA = 'application/data'
191 MIME_TEXT = 'text/plain'
193 INVENTORY_XML_ROOT = "system-status-inventory"
194 INVENTORY_XML_SUMMARY = 'system-summary'
195 INVENTORY_XML_ELEMENT = 'inventory-entry'
196 CAP_XML_ROOT = "system-status-capabilities"
197 CAP_XML_ELEMENT = 'capability'
200 CAP_BOOT_LOADER = 'boot-loader'
201 CAP_DISK_INFO = 'disk-info'
202 CAP_HARDWARE_INFO = 'hardware-info'
203 CAP_KERNEL_INFO = 'kernel-info'
204 CAP_LOSETUP_A = 'loopback-devices'
205 CAP_MULTIPATH = 'multipath'
206 CAP_NETWORK_CONFIG = 'network-config'
207 CAP_NETWORK_INFO = 'network-info'
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_INFO, PII_YES, max_size=50*MB,
247 cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1,
249 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
251 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
253 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
255 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
257 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
260 ANSWER_YES_TO_ALL = False
264 dev_null = open('/dev/null', 'r+')
272 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
274 def cmd_output(cap, args, label=None, filter=None, binary=False):
277 if isinstance(args, list):
278 a = [aa for aa in args]
279 a[0] = os.path.basename(a[0])
283 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
286 def file_output(cap, path_list, newest_first=False):
288 If newest_first is True, the list of files in path_list is sorted
289 by file modification time in descending order, else its sorted
294 for path in path_list:
299 path_entries.append((path, s))
301 mtime = lambda(path, stat): stat.st_mtime
302 path_entries.sort(key=mtime, reverse=newest_first)
303 for p in path_entries:
304 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
305 cap_sizes[cap] < caps[cap][MAX_SIZE]:
306 data[p] = {'cap': cap, 'filename': p[0]}
307 cap_sizes[cap] += p[1].st_size
309 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
311 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
313 Walks the directory tree rooted at path. Files in current dir are processed
314 before files in sub-dirs.
317 if os.path.exists(path):
318 for root, dirs, files in os.walk(path):
319 fns = [fn for fn in [os.path.join(root, f) for f in files]
320 if os.path.isfile(fn) and matches(fn, pattern, negate)]
321 file_output(cap, fns, newest_first=newest_first)
323 def func_output(cap, label, func):
325 t = str(func).split()
326 data[label] = {'cap': cap, 'func': func}
328 def log_output(cap, logs, newest_first=False):
330 file_output(cap, logs)
332 ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
333 newest_first=newest_first)
335 ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
336 newest_first=newest_first)
341 for (k, v) in data.items():
343 if v.has_key('cmd_args'):
344 v['output'] = StringIOmtime()
345 if not process_lists.has_key(cap):
346 process_lists[cap] = []
347 process_lists[cap].append(
348 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
349 v['filter'], v['binary']))
350 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
351 # proc files must be read into memory
353 f = open(v['filename'], 'r')
356 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
357 cap_sizes[cap] < caps[cap][MAX_SIZE]:
358 v['output'] = StringIOmtime(s)
359 cap_sizes[cap] += len(s)
361 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
364 elif v.has_key('func'):
369 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
370 cap_sizes[cap] < caps[cap][MAX_SIZE]:
371 v['output'] = StringIOmtime(s)
372 cap_sizes[cap] += len(s)
374 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
376 run_procs(process_lists.values())
380 global ANSWER_YES_TO_ALL, SILENT_MODE
381 global entries, data, dbg, unlimited_data, log_days
384 only_ovs_info = False
385 collect_all_info = True
387 # we need access to privileged files, exit if we are not running as root
389 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
393 output_type = 'tar.gz'
400 (options, params) = getopt.gnu_getopt(
401 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
402 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
403 'debug', 'ovs', 'log-days='])
404 except getopt.GetoptError, opterr:
405 print >>sys.stderr, opterr
413 entries = [e for e in caps.keys() if caps[e][CHECKED]]
415 for (k, v) in options:
416 if k == '--capabilities':
417 update_capabilities()
422 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
425 print >>sys.stderr, "Invalid output format '%s'" % v
428 # "-s" or "--silent" means suppress output (except for the final
429 # output filename at the end)
430 if k in ['-s', '--silent']:
433 if k == '--entries' and v != '':
434 entries = v.split(',')
436 # If the user runs the script with "-y" or "--yestoall" we don't ask
437 # all the really annoying questions.
438 if k in ['-y', '--yestoall']:
439 ANSWER_YES_TO_ALL = True
444 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
445 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
447 print >>sys.stderr, "Invalid output file descriptor", output_fd
454 entries = caps.keys()
455 elif k == '--unlimited':
456 unlimited_data = True
459 ProcOutput.debug = True
463 collect_all_info = False
465 if k == '--log-days':
469 print >>sys.stderr, "Invalid additional arguments", str(params)
472 if output_fd != -1 and output_type != 'tar':
473 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
476 if output_fd != -1 and output_file is not None:
477 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
480 if ANSWER_YES_TO_ALL:
481 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
484 This application will collate dmesg output, details of the
485 hardware configuration of your machine, information about the build of
486 openvswitch that you are using, plus, if you allow it, various logs.
488 The collated information will be saved as a .%s for archiving or
489 sending to a Technical Support Representative.
491 The logs may contain private information, and if you are at all
492 worried about that, you should exit now, or you should explicitly
493 exclude those logs from the archive.
497 # assemble potential data
499 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
500 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
501 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
503 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
504 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
505 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
506 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
507 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
508 if len(pidof('iscsid')) != 0:
509 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
510 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
511 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
512 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
513 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
514 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
516 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
517 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
518 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
519 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
520 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
521 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
522 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
525 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
526 PROC_FILESYSTEMS, PROC_CMDLINE])
527 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
528 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
529 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
530 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
531 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
533 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
535 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
536 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
537 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
538 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
539 if CAP_MULTIPATH in entries and collect_all_info:
540 dump_rdac_groups(CAP_MULTIPATH)
542 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
543 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
544 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
545 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
546 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
548 cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
549 cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
550 cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
551 cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
552 for dir in DHCP_LEASE_DIR:
553 tree_output(CAP_NETWORK_INFO, dir)
554 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
555 cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
556 for p in os.listdir('/sys/class/net/'):
558 f = open('/sys/class/net/%s/type' % p, 'r')
561 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
563 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
564 if not p.startswith('vif') and not p.startswith('tap'):
565 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
566 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
567 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
568 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
570 cmd_output(CAP_NETWORK_INFO,
571 [TC, '-s', '-d', 'class', 'show', 'dev', p])
574 tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
575 tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
576 cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
577 file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
578 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
579 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
581 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
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 = ([ 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 = ([ OPENVSWITCH_LOG_DIR + x for x in
590 ['ovs-vswitchd.log', 'ovsdb-server.log',
591 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
592 log_output(CAP_SYSTEM_LOGS, system_logs)
593 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
595 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
596 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
598 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
600 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
602 file_output(CAP_YUM, [YUM_LOG])
603 tree_output(CAP_YUM, YUM_REPOS_DIR)
604 cmd_output(CAP_YUM, [RPM, '-qa'])
605 file_output(CAP_YUM, [APT_SOURCES_LIST])
606 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
607 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
609 # Filter out ovs relevant information if --ovs option passed
610 # else collect all information
614 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
616 ovs_info_list = ['process-tree']
617 # We cannot use iteritems, since we modify 'data' as we pass through
618 for (k, v) in data.items():
624 if info not in ovs_info_list and cap not in ovs_info_caps:
628 filter = ",".join(filters)
633 load_plugins(filter=filter)
637 # permit the user to filter out data
638 # We cannot use iteritems, since we modify 'data' as we pass through
639 for (k, v) in sorted(data.items()):
645 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
648 # collect selected data now
649 output_ts('Running commands to collect data')
652 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
655 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
659 if output_file is None:
662 dirname = os.path.dirname(output_file)
663 if dirname and not os.path.exists(dirname):
670 output_ts('Creating output file')
672 if output_type.startswith('tar'):
673 make_tar(subdir, output_type, output_fd, output_file)
675 make_zip(subdir, output_file)
678 print >>sys.stderr, "Category sizes (max, actual):\n"
679 for c in caps.keys():
680 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
684 def dump_scsi_hosts(cap):
686 l = os.listdir('/sys/class/scsi_host')
692 f = open('/sys/class/scsi_host/%s/proc_name' % h)
693 procname = f.readline().strip("\n")
699 f = open('/sys/class/scsi_host/%s/model_name' % h)
700 modelname = f.readline().strip("\n")
706 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
710 def module_info(cap):
711 output = StringIO.StringIO()
712 modules = open(PROC_MODULES, 'r')
716 module = line.split()[0]
717 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
722 return output.getvalue()
725 def multipathd_topology(cap):
726 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
727 stdout=PIPE, stderr=dev_null)
728 stdout, stderr = pipe.communicate('show topology')
733 output = StringIO.StringIO()
734 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
738 if not procs[0].timed_out:
739 return output.getvalue().splitlines()
745 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
747 fh = open('/proc/'+d+'/cmdline')
749 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
751 if not num_fds in fd_dict:
752 fd_dict[num_fds] = []
753 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
756 keys = fd_dict.keys()
757 keys.sort(lambda a, b: int(b) - int(a))
759 output += "%s: %s\n" % (k, str(fd_dict[k]))
762 def dump_rdac_groups(cap):
763 output = StringIO.StringIO()
764 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
768 if not procs[0].timed_out:
770 for line in output.getvalue().splitlines():
771 if line.startswith('ID'):
773 elif line.startswith('----'):
776 group, _ = line.split(None, 1)
777 cmd_output(cap, [MPPUTIL, '-g', group])
779 def load_plugins(just_capabilities=False, filter=None):
780 def getText(nodelist):
782 for node in nodelist:
783 if node.nodeType == node.TEXT_NODE:
787 def getBoolAttr(el, attr, default=False):
789 val = el.getAttribute(attr).lower()
790 if val in ['true', 'false', 'yes', 'no']:
791 ret = val in ['true', 'yes']
794 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
795 if not caps.has_key(dir):
796 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
798 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
799 assert xmldoc.documentElement.tagName == "capability"
801 pii, min_size, max_size, min_time, max_time, mime = \
802 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
804 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
805 pii = xmldoc.documentElement.getAttribute("pii")
806 if xmldoc.documentElement.getAttribute("min_size") != '':
807 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
808 if xmldoc.documentElement.getAttribute("max_size") != '':
809 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
810 if xmldoc.documentElement.getAttribute("min_time") != '':
811 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
812 if xmldoc.documentElement.getAttribute("max_time") != '':
813 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
814 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
815 mime = xmldoc.documentElement.getAttribute("mime")
816 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
817 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
819 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
821 if just_capabilities:
824 plugdir = os.path.join(PLUGIN_DIR, dir)
825 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
826 xmldoc = parse(os.path.join(plugdir, file))
827 assert xmldoc.documentElement.tagName == "collect"
829 for el in xmldoc.documentElement.getElementsByTagName("*"):
830 filters_tmp = el.getAttribute("filters")
831 if filters_tmp == '':
834 filters = filters_tmp.split(',')
835 if not(filter is None or filter in filters):
837 if el.tagName == "files":
838 newest_first = getBoolAttr(el, 'newest_first')
839 if el.getAttribute("type") == "logs":
840 log_output(dir, getText(el.childNodes).split(),
841 newest_first=newest_first)
843 file_output(dir, getText(el.childNodes).split(),
844 newest_first=newest_first)
845 elif el.tagName == "directory":
846 pattern = el.getAttribute("pattern")
847 if pattern == '': pattern = None
848 negate = getBoolAttr(el, 'negate')
849 newest_first = getBoolAttr(el, 'newest_first')
850 tree_output(dir, getText(el.childNodes),
851 pattern and re.compile(pattern) or None,
852 negate=negate, newest_first=newest_first)
853 elif el.tagName == "command":
854 label = el.getAttribute("label")
855 if label == '': label = None
856 binary = getBoolAttr(el, 'binary')
857 cmd_output(dir, getText(el.childNodes), label, binary=binary)
859 def make_tar(subdir, suffix, output_fd, output_file):
860 global SILENT_MODE, data
863 if suffix == 'tar.bz2':
865 elif suffix == 'tar.gz':
869 if output_file is None:
870 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
872 filename = output_file
873 old_umask = os.umask(0077)
874 tf = tarfile.open(filename, mode)
877 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
880 for (k, v) in data.items():
882 tar_filename = os.path.join(subdir, construct_filename(k, v))
883 ti = tarfile.TarInfo(tar_filename)
888 if v.has_key('output'):
889 ti.mtime = v['output'].mtime
890 ti.size = len(v['output'].getvalue())
892 tf.addfile(ti, v['output'])
893 elif v.has_key('filename'):
894 s = os.stat(v['filename'])
895 ti.mtime = s.st_mtime
897 tf.addfile(ti, file(v['filename']))
904 output ('Writing tarball %s successful.' % filename)
909 def make_zip(subdir, output_file):
910 global SILENT_MODE, data
912 if output_file is None:
913 filename = "%s/%s.zip" % (BUG_DIR, subdir)
915 filename = output_file
916 old_umask = os.umask(0077)
917 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
921 for (k, v) in data.items():
923 dest = os.path.join(subdir, construct_filename(k, v))
925 if v.has_key('output'):
926 zf.writestr(dest, v['output'].getvalue())
928 if os.stat(v['filename']).st_size < 50:
929 compress_type = zipfile.ZIP_STORED
931 compress_type = zipfile.ZIP_DEFLATED
932 zf.write(v['filename'], dest, compress_type)
938 output ('Writing archive %s successful.' % filename)
943 def make_inventory(inventory, subdir):
944 document = getDOMImplementation().createDocument(
945 None, INVENTORY_XML_ROOT, None)
947 # create summary entry
948 s = document.createElement(INVENTORY_XML_SUMMARY)
949 user = os.getenv('SUDO_USER', os.getenv('USER'))
951 s.setAttribute('user', user)
952 s.setAttribute('date', time.strftime('%c'))
953 s.setAttribute('hostname', platform.node())
954 s.setAttribute('uname', ' '.join(platform.uname()))
955 s.setAttribute('uptime', commands.getoutput(UPTIME))
956 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
958 map(lambda (k, v): inventory_entry(document, subdir, k, v),
960 return document.toprettyxml()
962 def inventory_entry(document, subdir, k, v):
964 el = document.createElement(INVENTORY_XML_ELEMENT)
965 el.setAttribute('capability', v['cap'])
966 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
967 el.setAttribute('md5sum', md5sum(v))
968 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
975 if d.has_key('filename'):
976 f = open(d['filename'])
982 elif d.has_key('output'):
983 m.update(d['output'].getvalue())
987 def construct_filename(k, v):
988 if v.has_key('filename'):
989 if v['filename'][0] == '/':
990 return v['filename'][1:]
993 s = k.replace(' ', '-')
994 s = s.replace('--', '-')
995 s = s.replace('/', '%')
996 if s.find('.') == -1:
1001 def update_capabilities():
1004 def update_cap_size(cap, size):
1005 update_cap(cap, MIN_SIZE, size)
1006 update_cap(cap, MAX_SIZE, size)
1007 update_cap(cap, CHECKED, size > 0)
1010 def update_cap(cap, k, v):
1014 caps[cap] = tuple(l)
1017 def size_of_dir(d, pattern=None, negate=False):
1018 if os.path.isdir(d):
1019 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1025 def size_of_all(files, pattern=None, negate=False):
1026 return sum([size_of(f, pattern, negate) for f in files])
1029 def matches(f, pattern, negate):
1031 return not matches(f, pattern, False)
1033 return pattern is None or pattern.match(f)
1036 def size_of(f, pattern, negate):
1037 if os.path.isfile(f) and matches(f, pattern, negate):
1038 return os.stat(f)[6]
1040 return size_of_dir(f, pattern, negate)
1043 def print_capabilities():
1044 document = getDOMImplementation().createDocument(
1045 "ns", CAP_XML_ROOT, None)
1046 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1047 print document.toprettyxml()
1049 def capability(document, key):
1051 el = document.createElement(CAP_XML_ELEMENT)
1052 el.setAttribute('key', c[KEY])
1053 el.setAttribute('pii', c[PII])
1054 el.setAttribute('min-size', str(c[MIN_SIZE]))
1055 el.setAttribute('max-size', str(c[MAX_SIZE]))
1056 el.setAttribute('min-time', str(c[MIN_TIME]))
1057 el.setAttribute('max-time', str(c[MAX_TIME]))
1058 el.setAttribute('content-type', c[MIME])
1059 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1060 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1064 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1065 return '\n'.join([format % i for i in d.items()]) + '\n'
1069 yn = raw_input(prompt)
1071 return len(yn) == 0 or yn.lower()[0] == 'y'
1074 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1079 f = open('/proc/partitions')
1082 for line in f.readlines():
1083 (major, minor, blocks, name) = line.split()
1084 if int(major) < 254 and not partition_re.match(name):
1095 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1096 self.command = command
1097 self.max_time = max_time
1099 self.running = False
1101 self.timed_out = False
1103 self.timeout = int(time.time()) + self.max_time
1104 self.filter = filter
1105 self.filter_state = {}
1107 self.bufsize = 1048576 # 1MB buffer
1109 self.bufsize = 1 # line buffered
1115 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1118 self.timed_out = False
1120 if ProcOutput.debug:
1121 output_ts("Starting '%s'" % self.cmdAsStr())
1122 self.proc = Popen(self.command, bufsize=self.bufsize,
1123 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1124 shell=isinstance(self.command, str))
1125 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1126 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1130 output_ts("'%s' failed" % self.cmdAsStr())
1131 self.running = False
1134 def terminate(self):
1137 self.proc.stdout.close()
1138 os.kill(self.proc.pid, SIGTERM)
1142 self.running = False
1143 self.status = SIGTERM
1145 def read_line(self):
1147 if self.bufsize == 1:
1148 line = self.proc.stdout.readline()
1150 line = self.proc.stdout.read(self.bufsize)
1153 self.proc.stdout.close()
1154 self.status = self.proc.wait()
1156 self.running = False
1159 line = self.filter(line, self.filter_state)
1161 self.inst.write(line)
1163 def run_procs(procs):
1171 active_procs.append(p)
1172 pipes.append(p.proc.stdout)
1174 elif p.status == None and not p.failed and not p.timed_out:
1177 active_procs.append(p)
1178 pipes.append(p.proc.stdout)
1185 (i, o, x) = select(pipes, [], [], 1.0)
1186 now = int(time.time())
1188 # handle process output
1189 for p in active_procs:
1190 if p.proc.stdout in i:
1194 if p.running and now > p.timeout:
1195 output_ts("'%s' timed out" % p.cmdAsStr())
1197 p.inst.write("\n** timeout **\n")
1205 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1207 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1215 class StringIOmtime(StringIO.StringIO):
1216 def __init__(self, buf=''):
1217 StringIO.StringIO.__init__(self, buf)
1218 self.mtime = time.time()
1221 StringIO.StringIO.write(self, s)
1222 self.mtime = time.time()
1225 if __name__ == "__main__":
1228 except KeyboardInterrupt:
1229 print "\nInterrupted."