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_STATUS = 'network-status'
208 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
209 CAP_PROCESS_LIST = 'process-list'
210 CAP_SYSTEM_LOGS = 'system-logs'
211 CAP_SYSTEM_SERVICES = 'system-services'
219 unlimited_data = False
221 # Default value for the number of rotated logs.
224 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
225 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
226 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
231 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
233 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
235 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
237 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
239 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
240 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
242 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
243 min_size=0, max_size=40*KB)
244 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*MB,
246 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
248 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
250 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
252 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
254 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
257 ANSWER_YES_TO_ALL = False
261 dev_null = open('/dev/null', 'r+')
269 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
271 def cmd_output(cap, args, label=None, filter=None, binary=False):
274 if isinstance(args, list):
275 a = [aa for aa in args]
276 a[0] = os.path.basename(a[0])
280 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
283 def file_output(cap, path_list, newest_first=False):
285 If newest_first is True, the list of files in path_list is sorted
286 by file modification time in descending order, else its sorted
291 for path in path_list:
296 path_entries.append((path, s))
298 mtime = lambda(path, stat): stat.st_mtime
299 path_entries.sort(key=mtime, reverse=newest_first)
300 for p in path_entries:
301 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
302 cap_sizes[cap] < caps[cap][MAX_SIZE]:
303 data[p] = {'cap': cap, 'filename': p[0]}
304 cap_sizes[cap] += p[1].st_size
306 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
308 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
310 Walks the directory tree rooted at path. Files in current dir are processed
311 before files in sub-dirs.
314 if os.path.exists(path):
315 for root, dirs, files in os.walk(path):
316 fns = [fn for fn in [os.path.join(root, f) for f in files]
317 if os.path.isfile(fn) and matches(fn, pattern, negate)]
318 file_output(cap, fns, newest_first=newest_first)
320 def func_output(cap, label, func):
322 t = str(func).split()
323 data[label] = {'cap': cap, 'func': func}
325 def log_output(cap, logs, newest_first=False):
327 file_output(cap, logs)
329 ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
330 newest_first=newest_first)
332 ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
333 newest_first=newest_first)
338 for (k, v) in data.items():
340 if v.has_key('cmd_args'):
341 v['output'] = StringIOmtime()
342 if not process_lists.has_key(cap):
343 process_lists[cap] = []
344 process_lists[cap].append(
345 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
346 v['filter'], v['binary']))
347 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
348 # proc files must be read into memory
350 f = open(v['filename'], 'r')
353 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
354 cap_sizes[cap] < caps[cap][MAX_SIZE]:
355 v['output'] = StringIOmtime(s)
356 cap_sizes[cap] += len(s)
358 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
361 elif v.has_key('func'):
366 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
367 cap_sizes[cap] < caps[cap][MAX_SIZE]:
368 v['output'] = StringIOmtime(s)
369 cap_sizes[cap] += len(s)
371 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
373 run_procs(process_lists.values())
377 global ANSWER_YES_TO_ALL, SILENT_MODE
378 global entries, data, dbg, unlimited_data, log_days
381 only_ovs_info = False
382 collect_all_info = True
384 # we need access to privileged files, exit if we are not running as root
386 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
390 output_type = 'tar.gz'
397 (options, params) = getopt.gnu_getopt(
398 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
399 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
400 'debug', 'ovs', 'log-days='])
401 except getopt.GetoptError, opterr:
402 print >>sys.stderr, opterr
410 entries = [e for e in caps.keys() if caps[e][CHECKED]]
412 for (k, v) in options:
413 if k == '--capabilities':
414 update_capabilities()
419 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
422 print >>sys.stderr, "Invalid output format '%s'" % v
425 # "-s" or "--silent" means suppress output (except for the final
426 # output filename at the end)
427 if k in ['-s', '--silent']:
430 if k == '--entries' and v != '':
431 entries = v.split(',')
433 # If the user runs the script with "-y" or "--yestoall" we don't ask
434 # all the really annoying questions.
435 if k in ['-y', '--yestoall']:
436 ANSWER_YES_TO_ALL = True
441 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
442 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
444 print >>sys.stderr, "Invalid output file descriptor", output_fd
451 entries = caps.keys()
452 elif k == '--unlimited':
453 unlimited_data = True
456 ProcOutput.debug = True
460 collect_all_info = False
462 if k == '--log-days':
466 print >>sys.stderr, "Invalid additional arguments", str(params)
469 if output_fd != -1 and output_type != 'tar':
470 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
473 if output_fd != -1 and output_file is not None:
474 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
477 if ANSWER_YES_TO_ALL:
478 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
481 This application will collate dmesg output, details of the
482 hardware configuration of your machine, information about the build of
483 openvswitch that you are using, plus, if you allow it, various logs.
485 The collated information will be saved as a .%s for archiving or
486 sending to a Technical Support Representative.
488 The logs may contain private information, and if you are at all
489 worried about that, you should exit now, or you should explicitly
490 exclude those logs from the archive.
494 # assemble potential data
496 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
497 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
498 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
500 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
501 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
502 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
503 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
504 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
505 if len(pidof('iscsid')) != 0:
506 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
507 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
508 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
509 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
510 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
511 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
513 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
514 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
515 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
516 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
517 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
518 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
519 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
522 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
523 PROC_FILESYSTEMS, PROC_CMDLINE])
524 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
525 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
526 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
527 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
528 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
530 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
532 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
533 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
534 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
535 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
536 if CAP_MULTIPATH in entries and collect_all_info:
537 dump_rdac_groups(CAP_MULTIPATH)
539 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
540 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
541 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
542 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
543 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
545 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
546 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
547 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
548 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
549 for dir in DHCP_LEASE_DIR:
550 tree_output(CAP_NETWORK_STATUS, dir)
551 for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
552 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-t', table, '-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_DPCTL, 'dump-flows', d])
580 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
581 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
583 system_logs = ([ VAR_LOG_DIR + x for x in
584 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
585 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
586 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
587 ['ovs-vswitchd.log', 'ovsdb-server.log',
588 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
589 log_output(CAP_SYSTEM_LOGS, system_logs)
590 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
592 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
593 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
595 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
597 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
599 file_output(CAP_YUM, [YUM_LOG])
600 tree_output(CAP_YUM, YUM_REPOS_DIR)
601 cmd_output(CAP_YUM, [RPM, '-qa'])
602 file_output(CAP_YUM, [APT_SOURCES_LIST])
603 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
604 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
606 # Filter out ovs relevant information if --ovs option passed
607 # else collect all information
611 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
613 ovs_info_list = ['process-tree']
614 # We cannot use iteritems, since we modify 'data' as we pass through
615 for (k, v) in data.items():
621 if info not in ovs_info_list and cap not in ovs_info_caps:
625 filter = ",".join(filters)
630 load_plugins(filter=filter)
634 # permit the user to filter out data
635 # We cannot use iteritems, since we modify 'data' as we pass through
636 for (k, v) in sorted(data.items()):
642 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
645 # collect selected data now
646 output_ts('Running commands to collect data')
649 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
652 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
656 if output_file is None:
659 dirname = os.path.dirname(output_file)
660 if dirname and not os.path.exists(dirname):
667 output_ts('Creating output file')
669 if output_type.startswith('tar'):
670 make_tar(subdir, output_type, output_fd, output_file)
672 make_zip(subdir, output_file)
675 print >>sys.stderr, "Category sizes (max, actual):\n"
676 for c in caps.keys():
677 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
681 def dump_scsi_hosts(cap):
683 l = os.listdir('/sys/class/scsi_host')
689 f = open('/sys/class/scsi_host/%s/proc_name' % h)
690 procname = f.readline().strip("\n")
696 f = open('/sys/class/scsi_host/%s/model_name' % h)
697 modelname = f.readline().strip("\n")
703 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
707 def module_info(cap):
708 output = StringIO.StringIO()
709 modules = open(PROC_MODULES, 'r')
713 module = line.split()[0]
714 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
719 return output.getvalue()
722 def multipathd_topology(cap):
723 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
724 stdout=PIPE, stderr=dev_null)
725 stdout, stderr = pipe.communicate('show topology')
730 output = StringIO.StringIO()
731 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
735 if not procs[0].timed_out:
736 return output.getvalue().splitlines()
742 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
744 fh = open('/proc/'+d+'/cmdline')
746 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
748 if not num_fds in fd_dict:
749 fd_dict[num_fds] = []
750 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
753 keys = fd_dict.keys()
754 keys.sort(lambda a, b: int(b) - int(a))
756 output += "%s: %s\n" % (k, str(fd_dict[k]))
759 def dump_rdac_groups(cap):
760 output = StringIO.StringIO()
761 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
765 if not procs[0].timed_out:
767 for line in output.getvalue().splitlines():
768 if line.startswith('ID'):
770 elif line.startswith('----'):
773 group, _ = line.split(None, 1)
774 cmd_output(cap, [MPPUTIL, '-g', group])
776 def load_plugins(just_capabilities=False, filter=None):
777 def getText(nodelist):
779 for node in nodelist:
780 if node.nodeType == node.TEXT_NODE:
784 def getBoolAttr(el, attr, default=False):
786 val = el.getAttribute(attr).lower()
787 if val in ['true', 'false', 'yes', 'no']:
788 ret = val in ['true', 'yes']
791 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
792 if not caps.has_key(dir):
793 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
795 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
796 assert xmldoc.documentElement.tagName == "capability"
798 pii, min_size, max_size, min_time, max_time, mime = \
799 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
801 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
802 pii = xmldoc.documentElement.getAttribute("pii")
803 if xmldoc.documentElement.getAttribute("min_size") != '':
804 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
805 if xmldoc.documentElement.getAttribute("max_size") != '':
806 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
807 if xmldoc.documentElement.getAttribute("min_time") != '':
808 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
809 if xmldoc.documentElement.getAttribute("max_time") != '':
810 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
811 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
812 mime = xmldoc.documentElement.getAttribute("mime")
813 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
814 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
816 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
818 if just_capabilities:
821 plugdir = os.path.join(PLUGIN_DIR, dir)
822 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
823 xmldoc = parse(os.path.join(plugdir, file))
824 assert xmldoc.documentElement.tagName == "collect"
826 for el in xmldoc.documentElement.getElementsByTagName("*"):
827 filters_tmp = el.getAttribute("filters")
828 if filters_tmp == '':
831 filters = filters_tmp.split(',')
832 if not(filter is None or filter in filters):
834 if el.tagName == "files":
835 newest_first = getBoolAttr(el, 'newest_first')
836 if el.getAttribute("type") == "logs":
837 log_output(dir, getText(el.childNodes).split(),
838 newest_first=newest_first)
840 file_output(dir, getText(el.childNodes).split(),
841 newest_first=newest_first)
842 elif el.tagName == "directory":
843 pattern = el.getAttribute("pattern")
844 if pattern == '': pattern = None
845 negate = getBoolAttr(el, 'negate')
846 newest_first = getBoolAttr(el, 'newest_first')
847 tree_output(dir, getText(el.childNodes),
848 pattern and re.compile(pattern) or None,
849 negate=negate, newest_first=newest_first)
850 elif el.tagName == "command":
851 label = el.getAttribute("label")
852 if label == '': label = None
853 binary = getBoolAttr(el, 'binary')
854 cmd_output(dir, getText(el.childNodes), label, binary=binary)
856 def make_tar(subdir, suffix, output_fd, output_file):
857 global SILENT_MODE, data
860 if suffix == 'tar.bz2':
862 elif suffix == 'tar.gz':
866 if output_file is None:
867 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
869 filename = output_file
870 old_umask = os.umask(0077)
871 tf = tarfile.open(filename, mode)
874 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
877 for (k, v) in data.items():
879 tar_filename = os.path.join(subdir, construct_filename(k, v))
880 ti = tarfile.TarInfo(tar_filename)
885 if v.has_key('output'):
886 ti.mtime = v['output'].mtime
887 ti.size = len(v['output'].getvalue())
889 tf.addfile(ti, v['output'])
890 elif v.has_key('filename'):
891 s = os.stat(v['filename'])
892 ti.mtime = s.st_mtime
894 tf.addfile(ti, file(v['filename']))
901 output ('Writing tarball %s successful.' % filename)
906 def make_zip(subdir, output_file):
907 global SILENT_MODE, data
909 if output_file is None:
910 filename = "%s/%s.zip" % (BUG_DIR, subdir)
912 filename = output_file
913 old_umask = os.umask(0077)
914 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
918 for (k, v) in data.items():
920 dest = os.path.join(subdir, construct_filename(k, v))
922 if v.has_key('output'):
923 zf.writestr(dest, v['output'].getvalue())
925 if os.stat(v['filename']).st_size < 50:
926 compress_type = zipfile.ZIP_STORED
928 compress_type = zipfile.ZIP_DEFLATED
929 zf.write(v['filename'], dest, compress_type)
935 output ('Writing archive %s successful.' % filename)
940 def make_inventory(inventory, subdir):
941 document = getDOMImplementation().createDocument(
942 None, INVENTORY_XML_ROOT, None)
944 # create summary entry
945 s = document.createElement(INVENTORY_XML_SUMMARY)
946 user = os.getenv('SUDO_USER', os.getenv('USER'))
948 s.setAttribute('user', user)
949 s.setAttribute('date', time.strftime('%c'))
950 s.setAttribute('hostname', platform.node())
951 s.setAttribute('uname', ' '.join(platform.uname()))
952 s.setAttribute('uptime', commands.getoutput(UPTIME))
953 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
955 map(lambda (k, v): inventory_entry(document, subdir, k, v),
957 return document.toprettyxml()
959 def inventory_entry(document, subdir, k, v):
961 el = document.createElement(INVENTORY_XML_ELEMENT)
962 el.setAttribute('capability', v['cap'])
963 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
964 el.setAttribute('md5sum', md5sum(v))
965 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
972 if d.has_key('filename'):
973 f = open(d['filename'])
979 elif d.has_key('output'):
980 m.update(d['output'].getvalue())
984 def construct_filename(k, v):
985 if v.has_key('filename'):
986 if v['filename'][0] == '/':
987 return v['filename'][1:]
990 s = k.replace(' ', '-')
991 s = s.replace('--', '-')
992 s = s.replace('/', '%')
993 if s.find('.') == -1:
998 def update_capabilities():
1001 def update_cap_size(cap, size):
1002 update_cap(cap, MIN_SIZE, size)
1003 update_cap(cap, MAX_SIZE, size)
1004 update_cap(cap, CHECKED, size > 0)
1007 def update_cap(cap, k, v):
1011 caps[cap] = tuple(l)
1014 def size_of_dir(d, pattern=None, negate=False):
1015 if os.path.isdir(d):
1016 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1022 def size_of_all(files, pattern=None, negate=False):
1023 return sum([size_of(f, pattern, negate) for f in files])
1026 def matches(f, pattern, negate):
1028 return not matches(f, pattern, False)
1030 return pattern is None or pattern.match(f)
1033 def size_of(f, pattern, negate):
1034 if os.path.isfile(f) and matches(f, pattern, negate):
1035 return os.stat(f)[6]
1037 return size_of_dir(f, pattern, negate)
1040 def print_capabilities():
1041 document = getDOMImplementation().createDocument(
1042 "ns", CAP_XML_ROOT, None)
1043 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1044 print document.toprettyxml()
1046 def capability(document, key):
1048 el = document.createElement(CAP_XML_ELEMENT)
1049 el.setAttribute('key', c[KEY])
1050 el.setAttribute('pii', c[PII])
1051 el.setAttribute('min-size', str(c[MIN_SIZE]))
1052 el.setAttribute('max-size', str(c[MAX_SIZE]))
1053 el.setAttribute('min-time', str(c[MIN_TIME]))
1054 el.setAttribute('max-time', str(c[MAX_TIME]))
1055 el.setAttribute('content-type', c[MIME])
1056 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1057 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1061 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1062 return '\n'.join([format % i for i in d.items()]) + '\n'
1066 yn = raw_input(prompt)
1068 return len(yn) == 0 or yn.lower()[0] == 'y'
1071 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1076 f = open('/proc/partitions')
1079 for line in f.readlines():
1080 (major, minor, blocks, name) = line.split()
1081 if int(major) < 254 and not partition_re.match(name):
1092 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1093 self.command = command
1094 self.max_time = max_time
1096 self.running = False
1098 self.timed_out = False
1100 self.timeout = int(time.time()) + self.max_time
1101 self.filter = filter
1102 self.filter_state = {}
1104 self.bufsize = 1048576 # 1MB buffer
1106 self.bufsize = 1 # line buffered
1112 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1115 self.timed_out = False
1117 if ProcOutput.debug:
1118 output_ts("Starting '%s'" % self.cmdAsStr())
1119 self.proc = Popen(self.command, bufsize=self.bufsize,
1120 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1121 shell=isinstance(self.command, str))
1122 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1123 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1127 output_ts("'%s' failed" % self.cmdAsStr())
1128 self.running = False
1131 def terminate(self):
1134 self.proc.stdout.close()
1135 os.kill(self.proc.pid, SIGTERM)
1139 self.running = False
1140 self.status = SIGTERM
1142 def read_line(self):
1144 if self.bufsize == 1:
1145 line = self.proc.stdout.readline()
1147 line = self.proc.stdout.read(self.bufsize)
1150 self.proc.stdout.close()
1151 self.status = self.proc.wait()
1153 self.running = False
1156 line = self.filter(line, self.filter_state)
1158 self.inst.write(line)
1160 def run_procs(procs):
1168 active_procs.append(p)
1169 pipes.append(p.proc.stdout)
1171 elif p.status == None and not p.failed and not p.timed_out:
1174 active_procs.append(p)
1175 pipes.append(p.proc.stdout)
1182 (i, o, x) = select(pipes, [], [], 1.0)
1183 now = int(time.time())
1185 # handle process output
1186 for p in active_procs:
1187 if p.proc.stdout in i:
1191 if p.running and now > p.timeout:
1192 output_ts("'%s' timed out" % p.cmdAsStr())
1194 p.inst.write("\n** timeout **\n")
1202 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1204 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1212 class StringIOmtime(StringIO.StringIO):
1213 def __init__(self, buf=''):
1214 StringIO.StringIO.__init__(self, buf)
1215 self.mtime = time.time()
1218 StringIO.StringIO.write(self, s)
1219 self.mtime = time.time()
1222 if __name__ == "__main__":
1225 except KeyboardInterrupt:
1226 print "\nInterrupted."