3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011, 2012 Nicira, Inc.
20 # To add new entries to the bugtool, you need to:
22 # Create a new capability. These declare the new entry to the GUI, including
23 # the expected size, time to collect, privacy implications, and whether the
24 # capability should be selected by default. One capability may refer to
25 # multiple files, assuming that they can be reasonably grouped together, and
26 # have the same privacy implications. You need:
28 # A new CAP_ constant.
29 # A cap() invocation to declare the capability.
31 # You then need to add calls to main() to collect the files. These will
32 # typically be calls to the helpers file_output(), tree_output(), cmd_output(),
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
48 from xml.dom.minidom import parse, getDOMImplementation
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
61 OS_RELEASE = platform.release()
67 APT_SOURCES_LIST = "/etc/apt/sources.list"
68 APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
69 BUG_DIR = "/var/log/ovs-bugtool"
70 PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
71 GRUB_CONFIG = '/boot/grub/menu.lst'
72 BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
73 BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
74 PROC_PARTITIONS = '/proc/partitions'
76 PROC_MOUNTS = '/proc/mounts'
77 ISCSI_CONF = '/etc/iscsi/iscsid.conf'
78 ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
79 PROC_CPUINFO = '/proc/cpuinfo'
80 PROC_MEMINFO = '/proc/meminfo'
81 PROC_IOPORTS = '/proc/ioports'
82 PROC_INTERRUPTS = '/proc/interrupts'
83 PROC_SCSI = '/proc/scsi/scsi'
84 PROC_VERSION = '/proc/version'
85 PROC_MODULES = '/proc/modules'
86 PROC_DEVICES = '/proc/devices'
87 PROC_FILESYSTEMS = '/proc/filesystems'
88 PROC_CMDLINE = '/proc/cmdline'
89 PROC_CONFIG = '/proc/config.gz'
90 PROC_USB_DEV = '/proc/bus/usb/devices'
91 PROC_NET_BONDING_DIR = '/proc/net/bonding'
92 IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
93 ROUTE_RE = re.compile(r'^.*/route-.*')
94 SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
95 SYSCONFIG_NETWORK = '/etc/sysconfig/network'
96 SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
97 PROC_NET_VLAN_DIR = '/proc/net/vlan'
98 PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
99 MODPROBE_CONF = '/etc/modprobe.conf'
100 MODPROBE_DIR = '/etc/modprobe.d'
101 RESOLV_CONF = '/etc/resolv.conf'
102 MPP_CONF = '/etc/mpp.conf'
103 MULTIPATH_CONF = '/etc/multipath.conf'
104 NSSWITCH_CONF = '/etc/nsswitch.conf'
105 NTP_CONF = '/etc/ntp.conf'
106 IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
108 HOSTS_ALLOW = '/etc/hosts.allow'
109 HOSTS_DENY = '/etc/hosts.deny'
110 DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
111 OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
112 OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
113 OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
114 OPENVSWITCH_DEFAULT_CONTROLLER = '/etc/default/openvswitch-controller'
115 OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
116 OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
117 VAR_LOG_DIR = '/var/log/'
118 VAR_LOG_CORE_DIR = '/var/log/core'
119 YUM_LOG = '/var/log/yum.log'
120 YUM_REPOS_DIR = '/etc/yum.repos.d'
126 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
129 CHKCONFIG = 'chkconfig'
132 DMIDECODE = 'dmidecode'
134 DPKG_QUERY = 'dpkg-query'
138 IFCONFIG = 'ifconfig'
139 IPTABLES = 'iptables'
140 ISCSIADM = 'iscsiadm'
147 MULTIPATHD = 'multipathd'
149 OVS_DPCTL = 'ovs-dpctl'
150 OVS_OFCTL = 'ovs-ofctl'
151 OVS_VSCTL = 'ovs-vsctl'
152 OVS_APPCTL = 'ovs-appctl'
163 # PII -- Personally identifiable information. Of particular concern are
164 # things that would identify customers, or their network topology.
165 # Passwords are never to be included in any bug report, regardless of any PII
168 # NO -- No PII will be in these entries.
169 # YES -- PII will likely or certainly be in these entries.
170 # MAYBE -- The user may wish to audit these entries for PII.
171 # IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
172 # but since we encourage customers to edit these files, PII may have been
173 # introduced by the customer. This is used in particular for the networking
180 PII_IF_CUSTOMIZED = 'if_customized'
191 MIME_DATA = 'application/data'
192 MIME_TEXT = 'text/plain'
194 INVENTORY_XML_ROOT = "system-status-inventory"
195 INVENTORY_XML_SUMMARY = 'system-summary'
196 INVENTORY_XML_ELEMENT = 'inventory-entry'
197 CAP_XML_ROOT = "system-status-capabilities"
198 CAP_XML_ELEMENT = 'capability'
201 CAP_BOOT_LOADER = 'boot-loader'
202 CAP_DISK_INFO = 'disk-info'
203 CAP_HARDWARE_INFO = 'hardware-info'
204 CAP_KERNEL_INFO = 'kernel-info'
205 CAP_LOSETUP_A = 'loopback-devices'
206 CAP_MULTIPATH = 'multipath'
207 CAP_NETWORK_CONFIG = 'network-config'
208 CAP_NETWORK_STATUS = 'network-status'
209 CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
210 CAP_PROCESS_LIST = 'process-list'
211 CAP_SYSTEM_LOGS = 'system-logs'
212 CAP_SYSTEM_SERVICES = 'system-services'
220 unlimited_data = False
222 # Default value for the number of rotated logs.
225 def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
226 max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
227 caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
232 cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB,
234 cap(CAP_DISK_INFO, PII_MAYBE, max_size=50*KB,
236 cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB,
238 cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120*KB,
240 cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
241 cap(CAP_MULTIPATH, PII_MAYBE, max_size=20*KB,
243 cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED,
244 min_size=0, max_size=40*KB)
245 cap(CAP_NETWORK_STATUS, PII_YES, max_size=50*MB,
247 cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1,
249 cap(CAP_PROCESS_LIST, PII_YES, max_size=30*KB,
251 cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200*MB,
253 cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB,
255 cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10*KB,
258 ANSWER_YES_TO_ALL = False
262 dev_null = open('/dev/null', 'r+')
270 output("[%s] %s" % (time.strftime("%x %X %Z"), x))
272 def cmd_output(cap, args, label=None, filter=None, binary=False):
275 if isinstance(args, list):
276 a = [aa for aa in args]
277 a[0] = os.path.basename(a[0])
281 data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
284 def file_output(cap, path_list, newest_first=False):
286 If newest_first is True, the list of files in path_list is sorted
287 by file modification time in descending order, else its sorted
292 for path in path_list:
297 path_entries.append((path, s))
299 mtime = lambda(path, stat): stat.st_mtime
300 path_entries.sort(key=mtime, reverse=newest_first)
301 for p in path_entries:
302 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
303 cap_sizes[cap] < caps[cap][MAX_SIZE]:
304 data[p] = {'cap': cap, 'filename': p[0]}
305 cap_sizes[cap] += p[1].st_size
307 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
309 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
311 Walks the directory tree rooted at path. Files in current dir are processed
312 before files in sub-dirs.
315 if os.path.exists(path):
316 for root, dirs, files in os.walk(path):
317 fns = [fn for fn in [os.path.join(root, f) for f in files]
318 if os.path.isfile(fn) and matches(fn, pattern, negate)]
319 file_output(cap, fns, newest_first=newest_first)
321 def func_output(cap, label, func):
323 t = str(func).split()
324 data[label] = {'cap': cap, 'func': func}
326 def log_output(cap, logs, newest_first=False):
328 file_output(cap, logs)
330 ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
331 newest_first=newest_first)
333 ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
334 newest_first=newest_first)
339 for (k, v) in data.items():
341 if v.has_key('cmd_args'):
342 v['output'] = StringIOmtime()
343 if not process_lists.has_key(cap):
344 process_lists[cap] = []
345 process_lists[cap].append(
346 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
347 v['filter'], v['binary']))
348 elif v.has_key('filename') and v['filename'].startswith('/proc/'):
349 # proc files must be read into memory
351 f = open(v['filename'], 'r')
354 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
355 cap_sizes[cap] < caps[cap][MAX_SIZE]:
356 v['output'] = StringIOmtime(s)
357 cap_sizes[cap] += len(s)
359 output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
362 elif v.has_key('func'):
367 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
368 cap_sizes[cap] < caps[cap][MAX_SIZE]:
369 v['output'] = StringIOmtime(s)
370 cap_sizes[cap] += len(s)
372 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
374 run_procs(process_lists.values())
378 global ANSWER_YES_TO_ALL, SILENT_MODE
379 global entries, data, dbg, unlimited_data, log_days
382 only_ovs_info = False
383 collect_all_info = True
385 # we need access to privileged files, exit if we are not running as root
387 print >>sys.stderr, "Error: ovs-bugtool must be run as root"
391 output_type = 'tar.gz'
398 (options, params) = getopt.gnu_getopt(
399 argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
400 'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
401 'debug', 'ovs', 'log-days='])
402 except getopt.GetoptError, opterr:
403 print >>sys.stderr, opterr
411 entries = [e for e in caps.keys() if caps[e][CHECKED]]
413 for (k, v) in options:
414 if k == '--capabilities':
415 update_capabilities()
420 if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
423 print >>sys.stderr, "Invalid output format '%s'" % v
426 # "-s" or "--silent" means suppress output (except for the final
427 # output filename at the end)
428 if k in ['-s', '--silent']:
431 if k == '--entries' and v != '':
432 entries = v.split(',')
434 # If the user runs the script with "-y" or "--yestoall" we don't ask
435 # all the really annoying questions.
436 if k in ['-y', '--yestoall']:
437 ANSWER_YES_TO_ALL = True
442 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
443 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
445 print >>sys.stderr, "Invalid output file descriptor", output_fd
452 entries = caps.keys()
453 elif k == '--unlimited':
454 unlimited_data = True
457 ProcOutput.debug = True
461 collect_all_info = False
463 if k == '--log-days':
467 print >>sys.stderr, "Invalid additional arguments", str(params)
470 if output_fd != -1 and output_type != 'tar':
471 print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
474 if output_fd != -1 and output_file is not None:
475 print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
478 if ANSWER_YES_TO_ALL:
479 output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
482 This application will collate dmesg output, details of the
483 hardware configuration of your machine, information about the build of
484 openvswitch that you are using, plus, if you allow it, various logs.
486 The collated information will be saved as a .%s for archiving or
487 sending to a Technical Support Representative.
489 The logs may contain private information, and if you are at all
490 worried about that, you should exit now, or you should explicitly
491 exclude those logs from the archive.
495 # assemble potential data
497 file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
498 cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
499 cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
501 cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
502 file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
503 file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
504 cmd_output(CAP_DISK_INFO, [DF, '-alT'])
505 cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
506 if len(pidof('iscsid')) != 0:
507 cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
508 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
509 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
510 cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
511 cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
512 func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
514 file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
515 cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
516 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
517 cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
518 file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
519 file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
520 cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
523 file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
524 PROC_FILESYSTEMS, PROC_CMDLINE])
525 cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
526 cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
527 file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
528 tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
529 func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
531 cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
533 file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
534 cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
535 func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
536 cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
537 if CAP_MULTIPATH in entries and collect_all_info:
538 dump_rdac_groups(CAP_MULTIPATH)
540 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
541 tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
542 file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
543 file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
544 file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
546 cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a'])
547 cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n'])
548 cmd_output(CAP_NETWORK_STATUS, [ARP, '-n'])
549 cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an'])
550 for dir in DHCP_LEASE_DIR:
551 tree_output(CAP_NETWORK_STATUS, dir)
552 cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL'])
553 for p in os.listdir('/sys/class/net/'):
555 f = open('/sys/class/net/%s/type' % p, 'r')
558 if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
560 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p])
561 if not p.startswith('vif') and not p.startswith('tap'):
562 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p])
563 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p])
564 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p])
565 cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p])
567 cmd_output(CAP_NETWORK_STATUS,
568 [TC, '-s', '-d', 'class', 'show', 'dev', p])
571 tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR)
572 tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR)
573 cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc'])
574 file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT])
575 if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
576 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
578 cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
580 vspidfile = open(OPENVSWITCH_VSWITCHD_PID)
581 vspid = int(vspidfile.readline().strip())
583 for b in bond_list(vspid):
584 cmd_output(CAP_NETWORK_STATUS,
585 [OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % vspid, '-e' 'bond/show %s' % b],
586 'ovs-appctl-bond-show-%s.out' % b)
590 cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
591 func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
593 system_logs = ([ VAR_LOG_DIR + x for x in
594 ['crit.log', 'kern.log', 'daemon.log', 'user.log',
595 'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
596 ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
597 ['ovs-vswitchd.log', 'ovsdb-server.log',
598 'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
599 log_output(CAP_SYSTEM_LOGS, system_logs)
600 log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
602 if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
603 cmd_output(CAP_SYSTEM_LOGS, [DMESG])
605 cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
607 tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
609 file_output(CAP_YUM, [YUM_LOG])
610 tree_output(CAP_YUM, YUM_REPOS_DIR)
611 cmd_output(CAP_YUM, [RPM, '-qa'])
612 file_output(CAP_YUM, [APT_SOURCES_LIST])
613 tree_output(CAP_YUM, APT_SOURCES_LIST_D)
614 cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
616 # Filter out ovs relevant information if --ovs option passed
617 # else collect all information
621 ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
623 ovs_info_list = ['process-tree']
624 # We cannot use iteritems, since we modify 'data' as we pass through
625 for (k, v) in data.items():
631 if info not in ovs_info_list and cap not in ovs_info_caps:
635 filter = ",".join(filters)
640 load_plugins(filter=filter)
644 # permit the user to filter out data
645 # We cannot use iteritems, since we modify 'data' as we pass through
646 for (k, v) in sorted(data.items()):
652 if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
655 # collect selected data now
656 output_ts('Running commands to collect data')
659 subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
662 data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
666 if output_file is None:
669 dirname = os.path.dirname(output_file)
670 if dirname and not os.path.exists(dirname):
677 output_ts('Creating output file')
679 if output_type.startswith('tar'):
680 make_tar(subdir, output_type, output_fd, output_file)
682 make_zip(subdir, output_file)
685 print >>sys.stderr, "Category sizes (max, actual):\n"
686 for c in caps.keys():
687 print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE],
691 def dump_scsi_hosts(cap):
693 l = os.listdir('/sys/class/scsi_host')
699 f = open('/sys/class/scsi_host/%s/proc_name' % h)
700 procname = f.readline().strip("\n")
706 f = open('/sys/class/scsi_host/%s/model_name' % h)
707 modelname = f.readline().strip("\n")
713 output += " %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
717 def module_info(cap):
718 output = StringIO.StringIO()
719 modules = open(PROC_MODULES, 'r')
723 module = line.split()[0]
724 procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
729 return output.getvalue()
732 def multipathd_topology(cap):
733 pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
734 stdout=PIPE, stderr=dev_null)
735 stdout, stderr = pipe.communicate('show topology')
740 output = StringIO.StringIO()
741 procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
745 if not procs[0].timed_out:
746 return output.getvalue().splitlines()
750 output = StringIO.StringIO()
751 procs = [ProcOutput([OVS_APPCTL, '-t', '@RUNDIR@/ovs-vswitchd.%s.ctl' % pid, '-e' 'bond/list'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
755 if not procs[0].timed_out:
756 bonds = output.getvalue().splitlines()[1:]
757 return [x.split('\t')[1] for x in bonds]
763 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
765 fh = open('/proc/'+d+'/cmdline')
767 num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
769 if not num_fds in fd_dict:
770 fd_dict[num_fds] = []
771 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
774 keys = fd_dict.keys()
775 keys.sort(lambda a, b: int(b) - int(a))
777 output += "%s: %s\n" % (k, str(fd_dict[k]))
780 def dump_rdac_groups(cap):
781 output = StringIO.StringIO()
782 procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
786 if not procs[0].timed_out:
788 for line in output.getvalue().splitlines():
789 if line.startswith('ID'):
791 elif line.startswith('----'):
794 group, _ = line.split(None, 1)
795 cmd_output(cap, [MPPUTIL, '-g', group])
797 def load_plugins(just_capabilities=False, filter=None):
798 def getText(nodelist):
800 for node in nodelist:
801 if node.nodeType == node.TEXT_NODE:
805 def getBoolAttr(el, attr, default=False):
807 val = el.getAttribute(attr).lower()
808 if val in ['true', 'false', 'yes', 'no']:
809 ret = val in ['true', 'yes']
812 for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
813 if not caps.has_key(dir):
814 if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
816 xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
817 assert xmldoc.documentElement.tagName == "capability"
819 pii, min_size, max_size, min_time, max_time, mime = \
820 PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
822 if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
823 pii = xmldoc.documentElement.getAttribute("pii")
824 if xmldoc.documentElement.getAttribute("min_size") != '':
825 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
826 if xmldoc.documentElement.getAttribute("max_size") != '':
827 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
828 if xmldoc.documentElement.getAttribute("min_time") != '':
829 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
830 if xmldoc.documentElement.getAttribute("max_time") != '':
831 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
832 if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
833 mime = xmldoc.documentElement.getAttribute("mime")
834 checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
835 hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
837 cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
839 if just_capabilities:
842 plugdir = os.path.join(PLUGIN_DIR, dir)
843 for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
844 xmldoc = parse(os.path.join(plugdir, file))
845 assert xmldoc.documentElement.tagName == "collect"
847 for el in xmldoc.documentElement.getElementsByTagName("*"):
848 filters_tmp = el.getAttribute("filters")
849 if filters_tmp == '':
852 filters = filters_tmp.split(',')
853 if not(filter is None or filter in filters):
855 if el.tagName == "files":
856 newest_first = getBoolAttr(el, 'newest_first')
857 if el.getAttribute("type") == "logs":
858 log_output(dir, getText(el.childNodes).split(),
859 newest_first=newest_first)
861 file_output(dir, getText(el.childNodes).split(),
862 newest_first=newest_first)
863 elif el.tagName == "directory":
864 pattern = el.getAttribute("pattern")
865 if pattern == '': pattern = None
866 negate = getBoolAttr(el, 'negate')
867 newest_first = getBoolAttr(el, 'newest_first')
868 tree_output(dir, getText(el.childNodes),
869 pattern and re.compile(pattern) or None,
870 negate=negate, newest_first=newest_first)
871 elif el.tagName == "command":
872 label = el.getAttribute("label")
873 if label == '': label = None
874 binary = getBoolAttr(el, 'binary')
875 cmd_output(dir, getText(el.childNodes), label, binary=binary)
877 def make_tar(subdir, suffix, output_fd, output_file):
878 global SILENT_MODE, data
881 if suffix == 'tar.bz2':
883 elif suffix == 'tar.gz':
887 if output_file is None:
888 filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
890 filename = output_file
891 old_umask = os.umask(0077)
892 tf = tarfile.open(filename, mode)
895 tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
898 for (k, v) in data.items():
900 tar_filename = os.path.join(subdir, construct_filename(k, v))
901 ti = tarfile.TarInfo(tar_filename)
906 if v.has_key('output'):
907 ti.mtime = v['output'].mtime
908 ti.size = len(v['output'].getvalue())
910 tf.addfile(ti, v['output'])
911 elif v.has_key('filename'):
912 s = os.stat(v['filename'])
913 ti.mtime = s.st_mtime
915 tf.addfile(ti, file(v['filename']))
922 output ('Writing tarball %s successful.' % filename)
927 def make_zip(subdir, output_file):
928 global SILENT_MODE, data
930 if output_file is None:
931 filename = "%s/%s.zip" % (BUG_DIR, subdir)
933 filename = output_file
934 old_umask = os.umask(0077)
935 zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
939 for (k, v) in data.items():
941 dest = os.path.join(subdir, construct_filename(k, v))
943 if v.has_key('output'):
944 zf.writestr(dest, v['output'].getvalue())
946 if os.stat(v['filename']).st_size < 50:
947 compress_type = zipfile.ZIP_STORED
949 compress_type = zipfile.ZIP_DEFLATED
950 zf.write(v['filename'], dest, compress_type)
956 output ('Writing archive %s successful.' % filename)
961 def make_inventory(inventory, subdir):
962 document = getDOMImplementation().createDocument(
963 None, INVENTORY_XML_ROOT, None)
965 # create summary entry
966 s = document.createElement(INVENTORY_XML_SUMMARY)
967 user = os.getenv('SUDO_USER', os.getenv('USER'))
969 s.setAttribute('user', user)
970 s.setAttribute('date', time.strftime('%c'))
971 s.setAttribute('hostname', platform.node())
972 s.setAttribute('uname', ' '.join(platform.uname()))
973 s.setAttribute('uptime', commands.getoutput(UPTIME))
974 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
976 map(lambda (k, v): inventory_entry(document, subdir, k, v),
978 return document.toprettyxml()
980 def inventory_entry(document, subdir, k, v):
982 el = document.createElement(INVENTORY_XML_ELEMENT)
983 el.setAttribute('capability', v['cap'])
984 el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
985 el.setAttribute('md5sum', md5sum(v))
986 document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
993 if d.has_key('filename'):
994 f = open(d['filename'])
1000 elif d.has_key('output'):
1001 m.update(d['output'].getvalue())
1002 return m.hexdigest()
1005 def construct_filename(k, v):
1006 if v.has_key('filename'):
1007 if v['filename'][0] == '/':
1008 return v['filename'][1:]
1010 return v['filename']
1011 s = k.replace(' ', '-')
1012 s = s.replace('--', '-')
1013 s = s.replace('/', '%')
1014 if s.find('.') == -1:
1019 def update_capabilities():
1022 def update_cap_size(cap, size):
1023 update_cap(cap, MIN_SIZE, size)
1024 update_cap(cap, MAX_SIZE, size)
1025 update_cap(cap, CHECKED, size > 0)
1028 def update_cap(cap, k, v):
1032 caps[cap] = tuple(l)
1035 def size_of_dir(d, pattern=None, negate=False):
1036 if os.path.isdir(d):
1037 return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1043 def size_of_all(files, pattern=None, negate=False):
1044 return sum([size_of(f, pattern, negate) for f in files])
1047 def matches(f, pattern, negate):
1049 return not matches(f, pattern, False)
1051 return pattern is None or pattern.match(f)
1054 def size_of(f, pattern, negate):
1055 if os.path.isfile(f) and matches(f, pattern, negate):
1056 return os.stat(f)[6]
1058 return size_of_dir(f, pattern, negate)
1061 def print_capabilities():
1062 document = getDOMImplementation().createDocument(
1063 "ns", CAP_XML_ROOT, None)
1064 map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1065 print document.toprettyxml()
1067 def capability(document, key):
1069 el = document.createElement(CAP_XML_ELEMENT)
1070 el.setAttribute('key', c[KEY])
1071 el.setAttribute('pii', c[PII])
1072 el.setAttribute('min-size', str(c[MIN_SIZE]))
1073 el.setAttribute('max-size', str(c[MAX_SIZE]))
1074 el.setAttribute('min-time', str(c[MIN_TIME]))
1075 el.setAttribute('max-time', str(c[MAX_TIME]))
1076 el.setAttribute('content-type', c[MIME])
1077 el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1078 document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1082 format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1083 return '\n'.join([format % i for i in d.items()]) + '\n'
1087 yn = raw_input(prompt)
1089 return len(yn) == 0 or yn.lower()[0] == 'y'
1092 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1097 f = open('/proc/partitions')
1100 for line in f.readlines():
1101 (major, minor, blocks, name) = line.split()
1102 if int(major) < 254 and not partition_re.match(name):
1113 def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1114 self.command = command
1115 self.max_time = max_time
1117 self.running = False
1119 self.timed_out = False
1121 self.timeout = int(time.time()) + self.max_time
1122 self.filter = filter
1123 self.filter_state = {}
1125 self.bufsize = 1048576 # 1MB buffer
1127 self.bufsize = 1 # line buffered
1133 return isinstance(self.command, list) and ' '.join(self.command) or self.command
1136 self.timed_out = False
1138 if ProcOutput.debug:
1139 output_ts("Starting '%s'" % self.cmdAsStr())
1140 self.proc = Popen(self.command, bufsize=self.bufsize,
1141 stdin=dev_null, stdout=PIPE, stderr=dev_null,
1142 shell=isinstance(self.command, str))
1143 old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1144 fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1148 output_ts("'%s' failed" % self.cmdAsStr())
1149 self.running = False
1152 def terminate(self):
1155 self.proc.stdout.close()
1156 os.kill(self.proc.pid, SIGTERM)
1160 self.running = False
1161 self.status = SIGTERM
1163 def read_line(self):
1165 if self.bufsize == 1:
1166 line = self.proc.stdout.readline()
1168 line = self.proc.stdout.read(self.bufsize)
1171 self.proc.stdout.close()
1172 self.status = self.proc.wait()
1174 self.running = False
1177 line = self.filter(line, self.filter_state)
1179 self.inst.write(line)
1181 def run_procs(procs):
1189 active_procs.append(p)
1190 pipes.append(p.proc.stdout)
1192 elif p.status == None and not p.failed and not p.timed_out:
1195 active_procs.append(p)
1196 pipes.append(p.proc.stdout)
1203 (i, o, x) = select(pipes, [], [], 1.0)
1204 now = int(time.time())
1206 # handle process output
1207 for p in active_procs:
1208 if p.proc.stdout in i:
1212 if p.running and now > p.timeout:
1213 output_ts("'%s' timed out" % p.cmdAsStr())
1215 p.inst.write("\n** timeout **\n")
1223 for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1225 if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1233 class StringIOmtime(StringIO.StringIO):
1234 def __init__(self, buf=''):
1235 StringIO.StringIO.__init__(self, buf)
1236 self.mtime = time.time()
1239 StringIO.StringIO.write(self, s)
1240 self.mtime = time.time()
1243 if __name__ == "__main__":
1246 except KeyboardInterrupt:
1247 print "\nInterrupted."