ovs-bugtool: Remove duplicate bond/show command.
[sliver-openvswitch.git] / utilities / bugtool / ovs-bugtool.in
1 #! @PYTHON@
2
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.
6 #
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.
11 #
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
15 #
16 # Copyright (c) 2005, 2007 XenSource Ltd.
17 # Copyright (c) 2010, 2011, 2012 Nicira, Inc.
18
19 #
20 # To add new entries to the bugtool, you need to:
21 #
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:
27 #
28 #   A new CAP_ constant.
29 #   A cap() invocation to declare the capability.
30 #
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(),
33 # or func_output().
34 #
35
36 import warnings
37 warnings.filterwarnings(action="ignore", category=DeprecationWarning)
38
39 import getopt
40 import re
41 import os
42 import StringIO
43 import sys
44 import tarfile
45 import time
46 import commands
47 import pprint
48 from xml.dom.minidom import parse, getDOMImplementation
49 import zipfile
50 from subprocess import Popen, PIPE
51 from select import select
52 from signal import SIGTERM, SIGUSR1
53 import md5
54 import platform
55 import fcntl
56 import glob
57 import urllib
58 import socket
59 import base64
60
61 OS_RELEASE = platform.release()
62
63 #
64 # Files & directories
65 #
66
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'
75 FSTAB = '/etc/fstab'
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'
107 HOSTS = '/etc/hosts'
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'
121
122 #
123 # External programs
124 #
125
126 os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
127 ARP = 'arp'
128 CAT = 'cat'
129 CHKCONFIG = 'chkconfig'
130 DF = 'df'
131 DMESG = 'dmesg'
132 DMIDECODE = 'dmidecode'
133 DMSETUP = 'dmsetup'
134 DPKG_QUERY = 'dpkg-query'
135 ETHTOOL = 'ethtool'
136 FDISK = 'fdisk'
137 FIND = 'find'
138 IFCONFIG = 'ifconfig'
139 IPTABLES = 'iptables'
140 ISCSIADM = 'iscsiadm'
141 LOSETUP = 'losetup'
142 LS = 'ls'
143 LSPCI = 'lspci'
144 MD5SUM = 'md5sum'
145 MODINFO = 'modinfo'
146 MPPUTIL = 'mppUtil'
147 MULTIPATHD = 'multipathd'
148 NETSTAT = 'netstat'
149 OVS_DPCTL = 'ovs-dpctl'
150 OVS_OFCTL = 'ovs-ofctl'
151 OVS_VSCTL = 'ovs-vsctl'
152 PS = 'ps'
153 ROUTE = 'route'
154 RPM = 'rpm'
155 SG_MAP = 'sg_map'
156 SYSCTL = 'sysctl'
157 TC = 'tc'
158 UPTIME = 'uptime'
159 ZCAT = 'zcat'
160
161 #
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
165 # declaration.
166 #
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
173 # scripts in dom0.
174 #
175
176 PII_NO            = 'no'
177 PII_YES           = 'yes'
178 PII_MAYBE         = 'maybe'
179 PII_IF_CUSTOMIZED = 'if_customized'
180 KEY      = 0
181 PII      = 1
182 MIN_SIZE = 2
183 MAX_SIZE = 3
184 MIN_TIME = 4
185 MAX_TIME = 5
186 MIME     = 6
187 CHECKED  = 7
188 HIDDEN   = 8
189
190 MIME_DATA = 'application/data'
191 MIME_TEXT = 'text/plain'
192
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'
198
199
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'
212 CAP_YUM                  = 'yum'
213
214 KB = 1024
215 MB = 1024 * 1024
216
217 caps = {}
218 cap_sizes = {}
219 unlimited_data = False
220 dbg = False
221 # Default value for the number of rotated logs.
222 log_days = 20
223
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,
227                  checked, hidden)
228     cap_sizes[key] = 0
229
230
231 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
232     max_time=5)
233 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
234     max_time=20)
235 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=30*KB,
236     max_time=20)
237 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
238     max_time=5)
239 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
240 cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
241     max_time=10)
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,
245     max_time=30)
246 cap(CAP_OPENVSWITCH_LOGS,    PII_MAYBE,                 max_size=-1,
247     max_time=5)
248 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
249     max_time=20)
250 cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=200*MB,
251     max_time=5)
252 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
253     max_time=20)
254 cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
255     max_time=30)
256
257 ANSWER_YES_TO_ALL = False
258 SILENT_MODE = False
259 entries = None
260 data = {}
261 dev_null = open('/dev/null', 'r+')
262
263 def output(x):
264     global SILENT_MODE
265     if not SILENT_MODE:
266         print x
267
268 def output_ts(x):
269     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
270
271 def cmd_output(cap, args, label=None, filter=None, binary=False):
272     if cap in entries:
273         if not label:
274             if isinstance(args, list):
275                 a = [aa for aa in args]
276                 a[0] = os.path.basename(a[0])
277                 label = ' '.join(a)
278             else:
279                 label = args
280         data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
281                        'binary': binary}
282
283 def file_output(cap, path_list, newest_first=False):
284     """
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
287     in ascending order.
288     """
289     if cap in entries:
290         path_entries = []
291         for path in path_list:
292             try:
293                 s = os.stat(path)
294             except OSError, e:
295                 continue
296             path_entries.append((path, s))
297
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
305             else:
306                 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
307
308 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
309     """
310     Walks the directory tree rooted at path. Files in current dir are processed
311     before files in sub-dirs.
312     """
313     if cap in entries:
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)
319
320 def func_output(cap, label, func):
321     if cap in entries:
322         t = str(func).split()
323         data[label] = {'cap': cap, 'func': func}
324
325 def log_output(cap, logs, newest_first=False):
326     global log_days
327     file_output(cap, logs)
328     file_output(cap,
329         ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
330         newest_first=newest_first)
331     file_output(cap,
332         ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
333         newest_first=newest_first)
334
335 def collect_data():
336     process_lists = {}
337
338     for (k, v) in data.items():
339         cap = v['cap']
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
349             try:
350                 f = open(v['filename'], 'r')
351                 s = f.read()
352                 f.close()
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)
357                 else:
358                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
359             except:
360                 pass
361         elif v.has_key('func'):
362             try:
363                 s = v['func'](cap)
364             except Exception, e:
365                 s = str(e)
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)
370             else:
371                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
372
373     run_procs(process_lists.values())
374
375
376 def main(argv=None):
377     global ANSWER_YES_TO_ALL, SILENT_MODE
378     global entries, data, dbg, unlimited_data, log_days
379
380     # Filter flags
381     only_ovs_info = False
382     collect_all_info = True
383
384     # we need access to privileged files, exit if we are not running as root
385     if os.getuid() != 0:
386         print >>sys.stderr, "Error: ovs-bugtool must be run as root"
387         return 1
388
389     output_file = None
390     output_type = 'tar.gz'
391     output_fd = -1
392
393     if argv is None:
394         argv = sys.argv
395
396     try:
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
403         return 2
404
405     try:
406         load_plugins(True)
407     except:
408         pass
409
410     entries = [e for e in caps.keys() if caps[e][CHECKED]]
411
412     for (k, v) in options:
413         if k == '--capabilities':
414             update_capabilities()
415             print_capabilities()
416             return 0
417
418         if k == '--output':
419             if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
420                 output_type = v
421             else:
422                 print >>sys.stderr, "Invalid output format '%s'" % v
423                 return 2
424
425         # "-s" or "--silent" means suppress output (except for the final
426         # output filename at the end)
427         if k in ['-s', '--silent']:
428             SILENT_MODE = True
429
430         if k == '--entries' and v != '':
431             entries = v.split(',')
432
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
437
438         if k == '--outfd':
439             output_fd = int(v)
440             try:
441                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
442                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
443             except:
444                 print >>sys.stderr, "Invalid output file descriptor", output_fd
445                 return 2
446
447         if k == '--outfile':
448             output_file = v
449
450         elif k == '--all':
451             entries = caps.keys()
452         elif k == '--unlimited':
453             unlimited_data = True
454         elif k == '--debug':
455             dbg = True
456             ProcOutput.debug = True
457
458         if k == '--ovs':
459             only_ovs_info = True
460             collect_all_info = False
461
462         if k == '--log-days':
463             log_days = int(v)
464
465     if len(params) != 1:
466         print >>sys.stderr, "Invalid additional arguments", str(params)
467         return 2
468
469     if output_fd != -1 and output_type != 'tar':
470         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
471         return 2
472
473     if output_fd != -1 and output_file is not None:
474         print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
475         return 2
476
477     if ANSWER_YES_TO_ALL:
478         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
479
480     output('''
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.
484
485 The collated information will be saved as a .%s for archiving or
486 sending to a Technical Support Representative.
487
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.
491
492 ''' % output_type)
493
494     # assemble potential data
495
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')
499
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)
512
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'])
520
521
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)
529
530     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
531
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)
538
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])
544
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/'):
554         try:
555             f = open('/sys/class/net/%s/type' % p, 'r')
556             t = f.readline()
557             f.close()
558             if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
559                 # ARPHRD_ETHER
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])
566             if int(t) == 1:
567                 cmd_output(CAP_NETWORK_STATUS,
568                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
569         except:
570             pass
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'])
577         for d in dp_list():
578             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
579
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)
582
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)
591
592     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
593         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
594
595     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
596
597     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
598
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')
605
606     # Filter out ovs relevant information if --ovs option passed
607     # else collect all information
608     filters = set()
609     if only_ovs_info:
610         filters.add('ovs')
611         ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
612                          CAP_NETWORK_CONFIG]
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():
616             cap = v['cap']
617             if 'filename' in v:
618                 info = k[0]
619             else:
620                 info = k
621             if info not in ovs_info_list and cap not in ovs_info_caps:
622                 del data[k]
623
624     if filters:
625         filter = ",".join(filters)
626     else:
627         filter = None
628
629     try:
630         load_plugins(filter=filter)
631     except:
632         pass
633
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()):
637        cap = v['cap']
638        if 'filename' in v:
639            key = k[0]
640        else:
641            key = k
642        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
643            del data[k]
644
645     # collect selected data now
646     output_ts('Running commands to collect data')
647     collect_data()
648
649     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
650
651     # include inventory
652     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
653
654     # create archive
655     if output_fd == -1:
656         if output_file is None:
657             dirname = BUG_DIR
658         else:
659             dirname = os.path.dirname(output_file)
660         if dirname and not os.path.exists(dirname):
661             try:
662                 os.makedirs(dirname)
663             except:
664                 pass
665
666     if output_fd == -1:
667         output_ts('Creating output file')
668
669     if output_type.startswith('tar'):
670         make_tar(subdir, output_type, output_fd, output_file)
671     else:
672         make_zip(subdir, output_file)
673
674     if dbg:
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],
678                                                      cap_sizes[c])
679     return 0
680
681 def dump_scsi_hosts(cap):
682     output = ''
683     l = os.listdir('/sys/class/scsi_host')
684     l.sort()
685
686     for h in l:
687         procname = ''
688         try:
689                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
690                 procname = f.readline().strip("\n")
691                 f.close()
692         except:
693                 pass
694         modelname = None
695         try:
696                 f = open('/sys/class/scsi_host/%s/model_name' % h)
697                 modelname = f.readline().strip("\n")
698                 f.close()
699         except:
700                 pass
701
702         output += "%s:\n" %h
703         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
704
705     return output
706
707 def module_info(cap):
708     output = StringIO.StringIO()
709     modules = open(PROC_MODULES, 'r')
710     procs = []
711
712     for line in modules:
713         module = line.split()[0]
714         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
715     modules.close()
716
717     run_procs([procs])
718
719     return output.getvalue()
720
721
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')
726
727     return stdout
728
729 def dp_list():
730     output = StringIO.StringIO()
731     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
732
733     run_procs([procs])
734
735     if not procs[0].timed_out:
736         return output.getvalue().splitlines()
737     return []
738
739 def fd_usage(cap):
740     output = ''
741     fd_dict = {}
742     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
743         try:
744             fh = open('/proc/'+d+'/cmdline')
745             name = fh.readline()
746             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
747             if num_fds > 0:
748                 if not num_fds in fd_dict:
749                     fd_dict[num_fds] = []
750                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
751         finally:
752             fh.close()
753     keys = fd_dict.keys()
754     keys.sort(lambda a, b: int(b) - int(a))
755     for k in keys:
756         output += "%s: %s\n" % (k, str(fd_dict[k]))
757     return output
758
759 def dump_rdac_groups(cap):
760     output = StringIO.StringIO()
761     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
762
763     run_procs([procs])
764
765     if not procs[0].timed_out:
766         proc_line = 0
767         for line in output.getvalue().splitlines():
768             if line.startswith('ID'):
769                 proc_line = 2
770             elif line.startswith('----'):
771                 proc_line -= 1
772             elif proc_line > 0:
773                 group, _ = line.split(None, 1)
774                 cmd_output(cap, [MPPUTIL, '-g', group])
775
776 def load_plugins(just_capabilities=False, filter=None):
777     def getText(nodelist):
778         rc = ""
779         for node in nodelist:
780             if node.nodeType == node.TEXT_NODE:
781                 rc += node.data
782         return rc.encode()
783
784     def getBoolAttr(el, attr, default=False):
785         ret = default
786         val = el.getAttribute(attr).lower()
787         if val in ['true', 'false', 'yes', 'no']:
788             ret = val in ['true', 'yes']
789         return ret
790
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)):
794                 continue
795             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
796             assert xmldoc.documentElement.tagName == "capability"
797
798             pii, min_size, max_size, min_time, max_time, mime = \
799                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
800
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)
815
816             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
817
818         if just_capabilities:
819             continue
820
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"
825
826             for el in xmldoc.documentElement.getElementsByTagName("*"):
827                 filters_tmp = el.getAttribute("filters")
828                 if filters_tmp == '':
829                     filters = []
830                 else:
831                     filters = filters_tmp.split(',')
832                 if not(filter is None or filter in filters):
833                     continue
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)
839                     else:
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)
855
856 def make_tar(subdir, suffix, output_fd, output_file):
857     global SILENT_MODE, data
858
859     mode = 'w'
860     if suffix == 'tar.bz2':
861         mode = 'w:bz2'
862     elif suffix == 'tar.gz':
863         mode = 'w:gz'
864
865     if output_fd == -1:
866         if output_file is None:
867             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
868         else:
869             filename = output_file
870         old_umask = os.umask(0077)
871         tf = tarfile.open(filename, mode)
872         os.umask(old_umask)
873     else:
874         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
875
876     try:
877         for (k, v) in data.items():
878             try:
879                 tar_filename = os.path.join(subdir, construct_filename(k, v))
880                 ti = tarfile.TarInfo(tar_filename)
881
882                 ti.uname = 'root'
883                 ti.gname = 'root'
884
885                 if v.has_key('output'):
886                     ti.mtime = v['output'].mtime
887                     ti.size = len(v['output'].getvalue())
888                     v['output'].seek(0)
889                     tf.addfile(ti, v['output'])
890                 elif v.has_key('filename'):
891                     s = os.stat(v['filename'])
892                     ti.mtime = s.st_mtime
893                     ti.size = s.st_size
894                     tf.addfile(ti, file(v['filename']))
895             except:
896                 pass
897     finally:
898         tf.close()
899
900     if output_fd == -1:
901         output ('Writing tarball %s successful.' % filename)
902         if SILENT_MODE:
903             print filename
904
905
906 def make_zip(subdir, output_file):
907     global SILENT_MODE, data
908
909     if output_file is None:
910         filename = "%s/%s.zip" % (BUG_DIR, subdir)
911     else:
912         filename = output_file
913     old_umask = os.umask(0077)
914     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
915     os.umask(old_umask)
916
917     try:
918         for (k, v) in data.items():
919             try:
920                 dest = os.path.join(subdir, construct_filename(k, v))
921
922                 if v.has_key('output'):
923                     zf.writestr(dest, v['output'].getvalue())
924                 else:
925                     if os.stat(v['filename']).st_size < 50:
926                         compress_type = zipfile.ZIP_STORED
927                     else:
928                         compress_type = zipfile.ZIP_DEFLATED
929                     zf.write(v['filename'], dest, compress_type)
930             except:
931                 pass
932     finally:
933         zf.close()
934
935     output ('Writing archive %s successful.' % filename)
936     if SILENT_MODE:
937         print filename
938
939
940 def make_inventory(inventory, subdir):
941     document = getDOMImplementation().createDocument(
942         None, INVENTORY_XML_ROOT, None)
943
944     # create summary entry
945     s = document.createElement(INVENTORY_XML_SUMMARY)
946     user = os.getenv('SUDO_USER', os.getenv('USER'))
947     if 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)
954
955     map(lambda (k, v): inventory_entry(document, subdir, k, v),
956         inventory.items())
957     return document.toprettyxml()
958
959 def inventory_entry(document, subdir, k, v):
960     try:
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)
966     except:
967         pass
968
969
970 def md5sum(d):
971     m = md5.new()
972     if d.has_key('filename'):
973         f = open(d['filename'])
974         data = f.read(1024)
975         while len(data) > 0:
976             m.update(data)
977             data = f.read(1024)
978         f.close()
979     elif d.has_key('output'):
980         m.update(d['output'].getvalue())
981     return m.hexdigest()
982
983
984 def construct_filename(k, v):
985     if v.has_key('filename'):
986         if v['filename'][0] == '/':
987             return v['filename'][1:]
988         else:
989             return v['filename']
990     s = k.replace(' ', '-')
991     s = s.replace('--', '-')
992     s = s.replace('/', '%')
993     if s.find('.') == -1:
994         s += '.out'
995
996     return s
997
998 def update_capabilities():
999     pass
1000
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)
1005
1006
1007 def update_cap(cap, k, v):
1008     global caps
1009     l = list(caps[cap])
1010     l[k] = v
1011     caps[cap] = tuple(l)
1012
1013
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)],
1017                            pattern, negate)
1018     else:
1019         return 0
1020
1021
1022 def size_of_all(files, pattern=None, negate=False):
1023     return sum([size_of(f, pattern, negate) for f in files])
1024
1025
1026 def matches(f, pattern, negate):
1027     if negate:
1028         return not matches(f, pattern, False)
1029     else:
1030         return pattern is None or pattern.match(f)
1031
1032
1033 def size_of(f, pattern, negate):
1034     if os.path.isfile(f) and matches(f, pattern, negate):
1035         return os.stat(f)[6]
1036     else:
1037         return size_of_dir(f, pattern, negate)
1038
1039
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()
1045
1046 def capability(document, key):
1047     c = caps[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)
1058
1059
1060 def prettyDict(d):
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'
1063
1064
1065 def yes(prompt):
1066     yn = raw_input(prompt)
1067
1068     return len(yn) == 0 or yn.lower()[0] == 'y'
1069
1070
1071 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1072
1073 def disk_list():
1074     disks = []
1075     try:
1076         f = open('/proc/partitions')
1077         f.readline()
1078         f.readline()
1079         for line in f.readlines():
1080             (major, minor, blocks, name) = line.split()
1081             if int(major) < 254 and not partition_re.match(name):
1082                 disks.append(name)
1083         f.close()
1084     except:
1085         pass
1086     return disks
1087
1088
1089 class ProcOutput:
1090     debug = False
1091
1092     def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1093         self.command = command
1094         self.max_time = max_time
1095         self.inst = inst
1096         self.running = False
1097         self.status = None
1098         self.timed_out = False
1099         self.failed = False
1100         self.timeout = int(time.time()) + self.max_time
1101         self.filter = filter
1102         self.filter_state = {}
1103         if binary:
1104             self.bufsize = 1048576  # 1MB buffer
1105         else:
1106             self.bufsize = 1        # line buffered
1107
1108     def __del__(self):
1109         self.terminate()
1110
1111     def cmdAsStr(self):
1112         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1113
1114     def run(self):
1115         self.timed_out = False
1116         try:
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)
1124             self.running = True
1125             self.failed = False
1126         except:
1127             output_ts("'%s' failed" % self.cmdAsStr())
1128             self.running = False
1129             self.failed = True
1130
1131     def terminate(self):
1132         if self.running:
1133             try:
1134                 self.proc.stdout.close()
1135                 os.kill(self.proc.pid, SIGTERM)
1136             except:
1137                 pass
1138             self.proc = None
1139             self.running = False
1140             self.status = SIGTERM
1141
1142     def read_line(self):
1143         assert self.running
1144         if self.bufsize == 1:
1145             line = self.proc.stdout.readline()
1146         else:
1147             line = self.proc.stdout.read(self.bufsize)
1148         if line == '':
1149             # process exited
1150             self.proc.stdout.close()
1151             self.status = self.proc.wait()
1152             self.proc = None
1153             self.running = False
1154         else:
1155             if self.filter:
1156                 line = self.filter(line, self.filter_state)
1157             if self.inst:
1158                 self.inst.write(line)
1159
1160 def run_procs(procs):
1161     while True:
1162         pipes = []
1163         active_procs = []
1164
1165         for pp in procs:
1166             for p in pp:
1167                 if p.running:
1168                     active_procs.append(p)
1169                     pipes.append(p.proc.stdout)
1170                     break
1171                 elif p.status == None and not p.failed and not p.timed_out:
1172                     p.run()
1173                     if p.running:
1174                         active_procs.append(p)
1175                         pipes.append(p.proc.stdout)
1176                         break
1177
1178         if len(pipes) == 0:
1179             # all finished
1180             break
1181
1182         (i, o, x) = select(pipes, [], [], 1.0)
1183         now = int(time.time())
1184
1185         # handle process output
1186         for p in active_procs:
1187             if p.proc.stdout in i:
1188                 p.read_line()
1189
1190             # handle timeout
1191             if p.running and now > p.timeout:
1192                 output_ts("'%s' timed out" % p.cmdAsStr())
1193                 if p.inst:
1194                     p.inst.write("\n** timeout **\n")
1195                 p.timed_out = True
1196                 p.terminate()
1197
1198
1199 def pidof(name):
1200     pids = []
1201
1202     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1203         try:
1204             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1205                 pids.append(int(d))
1206         except:
1207             pass
1208
1209     return pids
1210
1211
1212 class StringIOmtime(StringIO.StringIO):
1213     def __init__(self, buf=''):
1214         StringIO.StringIO.__init__(self, buf)
1215         self.mtime = time.time()
1216
1217     def write(self, s):
1218         StringIO.StringIO.write(self, s)
1219         self.mtime = time.time()
1220
1221
1222 if __name__ == "__main__":
1223     try:
1224         sys.exit(main())
1225     except KeyboardInterrupt:
1226         print "\nInterrupted."
1227         sys.exit(3)