3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011, 2012 Nicira, Inc.
20 # To add new entries to the bugtool, you need to:
22 # Create a new capability. These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default. One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications. You need:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
31 # You then need to add calls to main() to collect the files. These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
48 from xml.dom.minidom import parse, getDOMImplementation
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
61 OS_RELEASE = platform.release()
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
71 GRUB_CONFIG = '/boot/grub/menu.lst'
72 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
73 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
74 PROC_PARTITIONS = '/proc/partitions'
76 PROC_MOUNTS = '/proc/mounts'
77 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
78 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
79 PROC_CPUINFO = '/proc/cpuinfo'
80 PROC_MEMINFO = '/proc/meminfo'
81 PROC_IOPORTS = '/proc/ioports'
82 PROC_INTERRUPTS = '/proc/interrupts'
83 PROC_SCSI = '/proc/scsi/scsi'
84 PROC_VERSION = '/proc/version'
85 PROC_MODULES = '/proc/modules'
86 PROC_DEVICES = '/proc/devices'
87 PROC_FILESYSTEMS = '/proc/filesystems'
88 PROC_CMDLINE = '/proc/cmdline'
89 PROC_CONFIG = '/proc/config.gz'
90 PROC_USB_DEV = '/proc/bus/usb/devices'
91 PROC_NET_BONDING_DIR = '/proc/net/bonding'
92 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
93 ROUTE_RE = re.compile(r'^.*/route-.*')
94 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
95 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
96 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
97 PROC_NET_VLAN_DIR = '/proc/net/vlan'
98 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
99 MODPROBE_CONF = '/etc/modprobe.conf'
100 MODPROBE_DIR = '/etc/modprobe.d'
101 RESOLV_CONF = '/etc/resolv.conf'
102 MPP_CONF = '/etc/mpp.conf'
103 MULTIPATH_CONF = '/etc/multipath.conf'
104 NSSWITCH_CONF = '/etc/nsswitch.conf'
105 NTP_CONF = '/etc/ntp.conf'
106 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
108 HOSTS_ALLOW = '/etc/hosts.allow'
109 HOSTS_DENY = '/etc/hosts.deny'
110 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
111 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
112 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
113 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
114 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
115 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
116 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
117 VAR_LOG_DIR = '/var/log/'
118 VAR_LOG_CORE_DIR = '/var/log/core'
119 YUM_LOG = '/var/log/yum.log'
120 YUM_REPOS_DIR = '/etc/yum.repos.d'
126 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
129 CHKCONFIG = 'chkconfig'
132 DMIDECODE = 'dmidecode'
134 DPKG_QUERY = 'dpkg-query'
138 IFCONFIG = 'ifconfig'
139 IPTABLES = 'iptables'
140 ISCSIADM = 'iscsiadm'
147 MULTIPATHD = 'multipathd'
149 OVS_DPCTL = 'ovs-dpctl'
150 OVS_OFCTL = 'ovs-ofctl'
151 OVS_VSCTL = 'ovs-vsctl'
152 OVS_APPCTL = 'ovs-appctl'
163 # PII -- Personally identifiable information. Of particular concern are
164 # things that would identify customers, or their network topology.
165 # Passwords are never to be included in any bug report, regardless of any PII
168 # NO -- No PII will be in these entries.
169 # YES -- PII will likely or certainly be in these entries.
170 # MAYBE -- The user may wish to audit these entries for PII.
171 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
172 # but since we encourage customers to edit these files, PII may have been
173 # introduced by the customer. This is used in particular for the networking
180 PII_IF_CUSTOMIZED = 'if_customized'
191 MIME_DATA = 'application/data'
192 MIME_TEXT = 'text/plain'
194 INVENTORY_XML_ROOT = "system-status-inventory"
195 INVENTORY_XML_SUMMARY = 'system-summary'
196 INVENTORY_XML_ELEMENT = 'inventory-entry'
197 CAP_XML_ROOT = "system-status-capabilities"
198 CAP_XML_ELEMENT = 'capability'
201 CAP_BOOT_LOADER = 'boot-loader'
202 CAP_DISK_INFO = 'disk-info'
203 CAP_HARDWARE_INFO = 'hardware-info'
204 CAP_KERNEL_INFO = 'kernel-info'
205 CAP_LOSETUP_A = 'loopback-devices'
206 CAP_MULTIPATH = 'multipath'
207 CAP_NETWORK_CONFIG = 'network-config'
208 CAP_NETWORK_STATUS = 'network-status'
209 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
210 CAP_PROCESS_LIST = 'process-list'
211 CAP_SYSTEM_LOGS = 'system-logs'
212 CAP_SYSTEM_SERVICES = 'system-services'
220 unlimited_data = False
223 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
224 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
225 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
230 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
232 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
234 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
236 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
238 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
239 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
241 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
242 min_size=0, max_size=40*KB)
243 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*MB,
245 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
247 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
249 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
251 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
253 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
256 ANSWER_YES_TO_ALL = False
260 dev_null = open('/dev/null', 'r+')
268 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
270 def cmd_output(cap, args, label=None, filter=None):
273 if isinstance(args, list):
274 a = [aa for aa in args]
275 a[0] = os.path.basename(a[0])
279 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter}
281 def file_output(cap, path_list, newest_first=False):
283 If newest_first is True, the list of files in path_list is sorted
284 by file modification time in descending order, else its sorted
289 for path in path_list:
294 path_entries.append((path, s))
296 mtime = lambda(path, stat): stat.st_mtime
297 path_entries.sort(key=mtime, reverse=newest_first)
298 for p in path_entries:
299 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
300 cap_sizes[cap] < caps[cap][MAX_SIZE]:
301 data[p] = {'cap': cap, 'filename': p[0]}
302 cap_sizes[cap] += p[1].st_size
304 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
306 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
308 Walks the directory tree rooted at path. Files in current dir are processed
309 before files in sub-dirs.
312 if os.path.exists(path):
313 for root, dirs, files in os.walk(path):
314 fns = [fn for fn in [os.path.join(root, f) for f in files]
315 if os.path.isfile(fn) and matches(fn, pattern, negate)]
316 file_output(cap, fns, newest_first=newest_first)
318 def func_output(cap, label, func):
320 t = str(func).split()
321 data[label] = {'cap': cap, 'func': func}
326 for (k, v) in data.items():
328 if v.has_key('cmd_args'):
329 v['output'] = StringIOmtime()
330 if not process_lists.has_key(cap):
331 process_lists[cap] = []
332 process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter']))
333 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
334 # proc files must be read into memory
336 f = open(v['filename'], 'r')
339 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
340 cap_sizes[cap] < caps[cap][MAX_SIZE]:
341 v['output'] = StringIOmtime(s)
342 cap_sizes[cap] += len(s)
344 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
347 elif v.has_key('func'):
352 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
353 cap_sizes[cap] < caps[cap][MAX_SIZE]:
354 v['output'] = StringIOmtime(s)
355 cap_sizes[cap] += len(s)
357 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
359 run_procs(process_lists.values())
363 global ANSWER_YES_TO_ALL, SILENT_MODE
364 global entries, data, dbg, unlimited_data
367 only_ovs_info = False
368 collect_all_info = True
370 # we need access to privileged files, exit if we are not running as root
372 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
376 output_type = 'tar.gz'
379 # Default value for the number of rotated logs.
386 (options, params) = getopt.gnu_getopt(
387 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
388 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
389 'debug', 'ovs', 'log-days='])
390 except getopt.GetoptError, opterr:
391 print >>sys.stderr, opterr
399 entries = [e for e in caps.keys() if caps[e][CHECKED]]
401 for (k, v) in options:
402 if k == '--capabilities':
403 update_capabilities()
408 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
411 print >>sys.stderr, "Invalid output format '%s'" % v
414 # "-s" or "--silent" means suppress output (except for the final
415 # output filename at the end)
416 if k in ['-s', '--silent']:
419 if k == '--entries' and v != '':
420 entries = v.split(',')
422 # If the user runs the script with "-y" or "--yestoall" we don't ask
423 # all the really annoying questions.
424 if k in ['-y', '--yestoall']:
425 ANSWER_YES_TO_ALL = True
430 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
431 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
433 print >>sys.stderr, "Invalid output file descriptor", output_fd
440 entries = caps.keys()
441 elif k == '--unlimited':
442 unlimited_data = True
445 ProcOutput.debug = True
449 collect_all_info = False
451 if k == '--log-days':
452 log_days = int(v) + 1
455 print >>sys.stderr, "Invalid additional arguments", str(params)
458 if output_fd != -1 and output_type != 'tar':
459 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
462 if output_fd != -1 and output_file is not None:
463 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
466 if ANSWER_YES_TO_ALL:
467 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
470 This application will collate dmesg output, details of the
471 hardware configuration of your machine, information about the build of
472 openvswitch that you are using, plus, if you allow it, various logs.
474 The collated information will be saved as a .%s for archiving or
475 sending to a Technical Support Representative.
477 The logs may contain private information, and if you are at all
478 worried about that, you should exit now, or you should explicitly
479 exclude those logs from the archive.
483 # assemble potential data
485 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
486 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
487 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
489 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
490 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
491 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
492 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
493 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
494 if len(pidof('iscsid')) != 0:
495 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
496 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
497 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
498 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
499 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
500 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
502 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
503 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
504 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
505 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
506 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
507 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
508 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
511 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
512 PROC_FILESYSTEMS, PROC_CMDLINE])
513 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
514 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
515 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
516 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
517 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
519 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
521 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
522 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
523 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
524 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
525 if CAP_MULTIPATH in entries and collect_all_info:
526 dump_rdac_groups(CAP_MULTIPATH)
528 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
529 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
530 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
531 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
532 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
534 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
535 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
536 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
537 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
538 for dir in DHCP_LEASE_DIR:
539 tree_output(CAP_NETWORK_STATUS, dir)
540 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
541 for p in os.listdir('/sys/class/net/'):
543 f = open('/sys/class/net/%s/type' % p, 'r')
546 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
548 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
549 if not p.startswith('vif') and not p.startswith('tap'):
550 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
551 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
552 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
553 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
555 cmd_output(CAP_NETWORK_STATUS,
556 [TC, '-s', '-d', 'class', 'show', 'dev', p])
559 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
560 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
561 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
562 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
563 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
564 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
566 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'show', d])
567 cmd_output(CAP_NETWORK_STATUS, [OVS_OFCTL, 'dump-flows', d])
568 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
570 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
571 vspid = int(vspidfile.readline().strip())
573 for b in bond_list(vspid):
574 cmd_output(CAP_NETWORK_STATUS,
575 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
576 'ovs-appctl-bond-show-%s.out' % b)
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 = (CAP_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 = (CAP_OPENVSWITCH_LOGS, [ OPENVSWITCH_LOG_DIR + x for x in
587 ['ovs-vswitchd.log', 'ovsdb-server.log',
588 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log']])
589 for cap, logs in [system_logs, ovs_logs]:
590 file_output(cap, logs)
592 ['%s.%d' % (f, n) for n in range(log_days) for f in logs])
594 ['%s.%d.gz' % (f, n) for n in range(log_days) for f in logs])
596 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
597 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
599 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
601 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
603 file_output(CAP_YUM, [YUM_LOG])
604 tree_output(CAP_YUM, YUM_REPOS_DIR)
605 cmd_output(CAP_YUM, [RPM, '-qa'])
606 file_output(CAP_YUM, [APT_SOURCES_LIST])
607 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
608 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
610 # Filter out ovs relevant information if --ovs option passed
611 # else collect all information
615 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
617 ovs_info_list = ['process-tree']
618 # We cannot use iteritems, since we modify 'data' as we pass through
619 for (k, v) in data.items():
625 if info not in ovs_info_list and cap not in ovs_info_caps:
629 filter = ",".join(filters)
634 load_plugins(filter=filter)
638 # permit the user to filter out data
639 # We cannot use iteritems, since we modify 'data' as we pass through
640 for (k, v) in sorted(data.items()):
646 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
649 # collect selected data now
650 output_ts('Running commands to collect data')
653 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
656 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
660 if output_file is None:
663 dirname = os.path.dirname(output_file)
664 if dirname and not os.path.exists(dirname):
671 output_ts('Creating output file')
673 if output_type.startswith('tar'):
674 make_tar(subdir, output_type, output_fd, output_file)
676 make_zip(subdir, output_file)
679 print >>sys.stderr, "Category sizes (max, actual):\n"
680 for c in caps.keys():
681 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
685 def dump_scsi_hosts(cap):
687 l = os.listdir('/sys/class/scsi_host')
693 f = open('/sys/class/scsi_host/%s/proc_name' % h)
694 procname = f.readline().strip("\n")
700 f = open('/sys/class/scsi_host/%s/model_name' % h)
701 modelname = f.readline().strip("\n")
707 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
711 def module_info(cap):
712 output = StringIO.StringIO()
713 modules = open(PROC_MODULES, 'r')
717 module = line.split()[0]
718 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
723 return output.getvalue()
726 def multipathd_topology(cap):
727 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
728 stdout=PIPE, stderr=dev_null)
729 stdout, stderr = pipe.communicate('show topology')
734 output = StringIO.StringIO()
735 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
739 if not procs[0].timed_out:
740 return output.getvalue().splitlines()
744 output = StringIO.StringIO()
745 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
749 if not procs[0].timed_out:
750 bonds = output.getvalue().splitlines()[1:]
751 return [x.split('\t')[1] for x in bonds]
757 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
759 fh = open('/proc/'+d+'/cmdline')
761 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
763 if not num_fds in fd_dict:
764 fd_dict[num_fds] = []
765 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
768 keys = fd_dict.keys()
769 keys.sort(lambda a, b: int(b) - int(a))
771 output += "%s: %s\n" % (k, str(fd_dict[k]))
774 def dump_rdac_groups(cap):
775 output = StringIO.StringIO()
776 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
780 if not procs[0].timed_out:
782 for line in output.getvalue().splitlines():
783 if line.startswith('ID'):
785 elif line.startswith('----'):
788 group, _ = line.split(None, 1)
789 cmd_output(cap, [MPPUTIL, '-g', group])
791 def load_plugins(just_capabilities=False, filter=None):
792 def getText(nodelist):
794 for node in nodelist:
795 if node.nodeType == node.TEXT_NODE:
799 def getBoolAttr(el, attr, default=False):
801 val = el.getAttribute(attr).lower()
802 if val in ['true', 'false', 'yes', 'no']:
803 ret = val in ['true', 'yes']
806 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
807 if not caps.has_key(dir):
808 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
810 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
811 assert xmldoc.documentElement.tagName == "capability"
813 pii, min_size, max_size, min_time, max_time, mime = \
814 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
816 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
817 pii = xmldoc.documentElement.getAttribute("pii")
818 if xmldoc.documentElement.getAttribute("min_size") != '':
819 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
820 if xmldoc.documentElement.getAttribute("max_size") != '':
821 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
822 if xmldoc.documentElement.getAttribute("min_time") != '':
823 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
824 if xmldoc.documentElement.getAttribute("max_time") != '':
825 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
826 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
827 mime = xmldoc.documentElement.getAttribute("mime")
828 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
829 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
831 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
833 if just_capabilities:
836 plugdir = os.path.join(PLUGIN_DIR, dir)
837 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
838 xmldoc = parse(os.path.join(plugdir, file))
839 assert xmldoc.documentElement.tagName == "collect"
841 for el in xmldoc.documentElement.getElementsByTagName("*"):
842 filters_tmp = el.getAttribute("filters")
843 if filters_tmp == '':
846 filters = filters_tmp.split(',')
847 if not(filter is None or filter in filters):
849 if el.tagName == "files":
850 newest_first = getBoolAttr(el, 'newest_first')
851 file_output(dir, getText(el.childNodes).split(),
852 newest_first=newest_first)
853 elif el.tagName == "directory":
854 pattern = el.getAttribute("pattern")
855 if pattern == '': pattern = None
856 negate = getBoolAttr(el, 'negate')
857 newest_first = getBoolAttr(el, 'newest_first')
858 tree_output(dir, getText(el.childNodes),
859 pattern and re.compile(pattern) or None,
860 negate=negate, newest_first=newest_first)
861 elif el.tagName == "command":
862 label = el.getAttribute("label")
863 if label == '': label = None
864 cmd_output(dir, getText(el.childNodes), label)
866 def make_tar(subdir, suffix, output_fd, output_file):
867 global SILENT_MODE, data
870 if suffix == 'tar.bz2':
872 elif suffix == 'tar.gz':
876 if output_file is None:
877 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
879 filename = output_file
880 old_umask = os.umask(0077)
881 tf = tarfile.open(filename, mode)
884 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
887 for (k, v) in data.items():
889 tar_filename = os.path.join(subdir, construct_filename(k, v))
890 ti = tarfile.TarInfo(tar_filename)
895 if v.has_key('output'):
896 ti.mtime = v['output'].mtime
897 ti.size = len(v['output'].getvalue())
899 tf.addfile(ti, v['output'])
900 elif v.has_key('filename'):
901 s = os.stat(v['filename'])
902 ti.mtime = s.st_mtime
904 tf.addfile(ti, file(v['filename']))
911 output ('Writing tarball %s successful.' % filename)
916 def make_zip(subdir, output_file):
917 global SILENT_MODE, data
919 if output_file is None:
920 filename = "%s/%s.zip" % (BUG_DIR, subdir)
922 filename = output_file
923 old_umask = os.umask(0077)
924 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
928 for (k, v) in data.items():
930 dest = os.path.join(subdir, construct_filename(k, v))
932 if v.has_key('output'):
933 zf.writestr(dest, v['output'].getvalue())
935 if os.stat(v['filename']).st_size < 50:
936 compress_type = zipfile.ZIP_STORED
938 compress_type = zipfile.ZIP_DEFLATED
939 zf.write(v['filename'], dest, compress_type)
945 output ('Writing archive %s successful.' % filename)
950 def make_inventory(inventory, subdir):
951 document = getDOMImplementation().createDocument(
952 None, INVENTORY_XML_ROOT, None)
954 # create summary entry
955 s = document.createElement(INVENTORY_XML_SUMMARY)
956 user = os.getenv('SUDO_USER', os.getenv('USER'))
958 s.setAttribute('user', user)
959 s.setAttribute('date', time.strftime('%c'))
960 s.setAttribute('hostname', platform.node())
961 s.setAttribute('uname', ' '.join(platform.uname()))
962 s.setAttribute('uptime', commands.getoutput(UPTIME))
963 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
965 map(lambda (k, v): inventory_entry(document, subdir, k, v),
967 return document.toprettyxml()
969 def inventory_entry(document, subdir, k, v):
971 el = document.createElement(INVENTORY_XML_ELEMENT)
972 el.setAttribute('capability', v['cap'])
973 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
974 el.setAttribute('md5sum', md5sum(v))
975 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
982 if d.has_key('filename'):
983 f = open(d['filename'])
989 elif d.has_key('output'):
990 m.update(d['output'].getvalue())
994 def construct_filename(k, v):
995 if v.has_key('filename'):
996 if v['filename'][0] == '/':
997 return v['filename'][1:]
1000 s = k.replace(' ', '-')
1001 s = s.replace('--', '-')
1002 s = s.replace('/', '%')
1003 if s.find('.') == -1:
1008 def update_capabilities():
1011 def update_cap_size(cap, size):
1012 update_cap(cap, MIN_SIZE, size)
1013 update_cap(cap, MAX_SIZE, size)
1014 update_cap(cap, CHECKED, size > 0)
1017 def update_cap(cap, k, v):
1021 caps[cap] = tuple(l)
1024 def size_of_dir(d, pattern=None, negate=False):
1025 if os.path.isdir(d):
1026 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1032 def size_of_all(files, pattern=None, negate=False):
1033 return sum([size_of(f, pattern, negate) for f in files])
1036 def matches(f, pattern, negate):
1038 return not matches(f, pattern, False)
1040 return pattern is None or pattern.match(f)
1043 def size_of(f, pattern, negate):
1044 if os.path.isfile(f) and matches(f, pattern, negate):
1045 return os.stat(f)[6]
1047 return size_of_dir(f, pattern, negate)
1050 def print_capabilities():
1051 document = getDOMImplementation().createDocument(
1052 "ns", CAP_XML_ROOT, None)
1053 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1054 print document.toprettyxml()
1056 def capability(document, key):
1058 el = document.createElement(CAP_XML_ELEMENT)
1059 el.setAttribute('key', c[KEY])
1060 el.setAttribute('pii', c[PII])
1061 el.setAttribute('min-size', str(c[MIN_SIZE]))
1062 el.setAttribute('max-size', str(c[MAX_SIZE]))
1063 el.setAttribute('min-time', str(c[MIN_TIME]))
1064 el.setAttribute('max-time', str(c[MAX_TIME]))
1065 el.setAttribute('content-type', c[MIME])
1066 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1067 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1071 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1072 return '\n'.join([format % i for i in d.items()]) + '\n'
1076 yn = raw_input(prompt)
1078 return len(yn) == 0 or yn.lower()[0] == 'y'
1081 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1086 f = open('/proc/partitions')
1089 for line in f.readlines():
1090 (major, minor, blocks, name) = line.split()
1091 if int(major) < 254 and not partition_re.match(name):
1102 def __init__(self, command, max_time, inst=None, filter=None):
1103 self.command = command
1104 self.max_time = max_time
1106 self.running = False
1108 self.timed_out = False
1110 self.timeout = int(time.time()) + self.max_time
1111 self.filter = filter
1112 self.filter_state = {}
1118 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1121 self.timed_out = False
1123 if ProcOutput.debug:
1124 output_ts("Starting '%s'" % self.cmdAsStr())
1125 self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null, shell=isinstance(self.command, str))
1126 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1127 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1131 output_ts("'%s' failed" % self.cmdAsStr())
1132 self.running = False
1135 def terminate(self):
1138 self.proc.stdout.close()
1139 os.kill(self.proc.pid, SIGTERM)
1143 self.running = False
1144 self.status = SIGTERM
1146 def read_line(self):
1148 line = self.proc.stdout.readline()
1151 self.proc.stdout.close()
1152 self.status = self.proc.wait()
1154 self.running = False
1157 line = self.filter(line, self.filter_state)
1159 self.inst.write(line)
1161 def run_procs(procs):
1169 active_procs.append(p)
1170 pipes.append(p.proc.stdout)
1172 elif p.status == None and not p.failed and not p.timed_out:
1175 active_procs.append(p)
1176 pipes.append(p.proc.stdout)
1183 (i, o, x) = select(pipes, [], [], 1.0)
1184 now = int(time.time())
1186 # handle process output
1187 for p in active_procs:
1188 if p.proc.stdout in i:
1192 if p.running and now > p.timeout:
1193 output_ts("'%s' timed out" % p.cmdAsStr())
1195 p.inst.write("\n** timeout **\n")
1203 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1205 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1213 class StringIOmtime(StringIO.StringIO):
1214 def __init__(self, buf=''):
1215 StringIO.StringIO.__init__(self, buf)
1216 self.mtime = time.time()
1219 StringIO.StringIO.write(self, s)
1220 self.mtime = time.time()
1223 if __name__ == "__main__":
1226 except KeyboardInterrupt:
1227 print "\nInterrupted."