ovs-bugtool: Increase max size of CAP_HARDWARE_INFO.
[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_INFO         = 'network-info'
208 CAP_NETWORK_STATUS       = 'network-status'
209 CAP_OPENVSWITCH_LOGS     = 'ovs-system-logs'
210 CAP_PROCESS_LIST         = 'process-list'
211 CAP_SYSTEM_LOGS          = 'system-logs'
212 CAP_SYSTEM_SERVICES      = 'system-services'
213 CAP_YUM                  = 'yum'
214
215 KB = 1024
216 MB = 1024 * 1024
217
218 caps = {}
219 cap_sizes = {}
220 unlimited_data = False
221 dbg = False
222 # Default value for the number of rotated logs.
223 log_days = 20
224
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,
228                  checked, hidden)
229     cap_sizes[key] = 0
230
231
232 cap(CAP_BOOT_LOADER,         PII_NO,                    max_size=3*KB,
233     max_time=5)
234 cap(CAP_DISK_INFO,           PII_MAYBE,                 max_size=50*KB,
235     max_time=20)
236 cap(CAP_HARDWARE_INFO,       PII_MAYBE,                 max_size=2*MB,
237     max_time=20)
238 cap(CAP_KERNEL_INFO,         PII_MAYBE,                 max_size=120*KB,
239     max_time=5)
240 cap(CAP_LOSETUP_A,           PII_MAYBE,                 max_size=KB, max_time=5)
241 cap(CAP_MULTIPATH,           PII_MAYBE,                 max_size=20*KB,
242     max_time=10)
243 cap(CAP_NETWORK_CONFIG,      PII_IF_CUSTOMIZED,
244                                         min_size=0,     max_size=40*KB)
245 cap(CAP_NETWORK_INFO,        PII_YES,                   max_size=50*MB,
246     max_time=30)
247 cap(CAP_NETWORK_STATUS,      PII_YES,                   max_size=-1,
248     max_time=30)
249 cap(CAP_OPENVSWITCH_LOGS,    PII_MAYBE,                 max_size=-1,
250     max_time=5)
251 cap(CAP_PROCESS_LIST,        PII_YES,                   max_size=30*KB,
252     max_time=20)
253 cap(CAP_SYSTEM_LOGS,         PII_MAYBE,                 max_size=200*MB,
254     max_time=5)
255 cap(CAP_SYSTEM_SERVICES,     PII_NO,                    max_size=5*KB,
256     max_time=20)
257 cap(CAP_YUM,                 PII_IF_CUSTOMIZED,         max_size=10*KB,
258     max_time=30)
259
260 ANSWER_YES_TO_ALL = False
261 SILENT_MODE = False
262 entries = None
263 data = {}
264 dev_null = open('/dev/null', 'r+')
265
266 def output(x):
267     global SILENT_MODE
268     if not SILENT_MODE:
269         print x
270
271 def output_ts(x):
272     output("[%s]  %s" % (time.strftime("%x %X %Z"), x))
273
274 def cmd_output(cap, args, label=None, filter=None, binary=False):
275     if cap in entries:
276         if not label:
277             if isinstance(args, list):
278                 a = [aa for aa in args]
279                 a[0] = os.path.basename(a[0])
280                 label = ' '.join(a)
281             else:
282                 label = args
283         data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
284                        'binary': binary}
285
286 def file_output(cap, path_list, newest_first=False):
287     """
288     If newest_first is True, the list of files in path_list is sorted
289     by file modification time in descending order, else its sorted
290     in ascending order.
291     """
292     if cap in entries:
293         path_entries = []
294         for path in path_list:
295             try:
296                 s = os.stat(path)
297             except OSError, e:
298                 continue
299             path_entries.append((path, s))
300
301         mtime = lambda(path, stat): stat.st_mtime
302         path_entries.sort(key=mtime, reverse=newest_first)
303         for p in path_entries:
304             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
305                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
306                 data[p] = {'cap': cap, 'filename': p[0]}
307                 cap_sizes[cap] += p[1].st_size
308             else:
309                 output("Omitting %s, size constraint of %s exceeded" % (p[0], cap))
310
311 def tree_output(cap, path, pattern=None, negate=False, newest_first=False):
312     """
313     Walks the directory tree rooted at path. Files in current dir are processed
314     before files in sub-dirs.
315     """
316     if cap in entries:
317         if os.path.exists(path):
318             for root, dirs, files in os.walk(path):
319                 fns = [fn for fn in [os.path.join(root, f) for f in files]
320                        if os.path.isfile(fn) and matches(fn, pattern, negate)]
321                 file_output(cap, fns, newest_first=newest_first)
322
323 def func_output(cap, label, func):
324     if cap in entries:
325         t = str(func).split()
326         data[label] = {'cap': cap, 'func': func}
327
328 def log_output(cap, logs, newest_first=False):
329     global log_days
330     file_output(cap, logs)
331     file_output(cap,
332         ['%s.%d' % (f, n) for n in range(1, log_days+1) for f in logs], \
333         newest_first=newest_first)
334     file_output(cap,
335         ['%s.%d.gz' % (f, n) for n in range(1, log_days+1) for f in logs], \
336         newest_first=newest_first)
337
338 def collect_data():
339     process_lists = {}
340
341     for (k, v) in data.items():
342         cap = v['cap']
343         if v.has_key('cmd_args'):
344             v['output'] = StringIOmtime()
345             if not process_lists.has_key(cap):
346                 process_lists[cap] = []
347             process_lists[cap].append(
348                 ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'],
349                            v['filter'], v['binary']))
350         elif v.has_key('filename') and v['filename'].startswith('/proc/'):
351             # proc files must be read into memory
352             try:
353                 f = open(v['filename'], 'r')
354                 s = f.read()
355                 f.close()
356                 if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
357                         cap_sizes[cap] < caps[cap][MAX_SIZE]:
358                     v['output'] = StringIOmtime(s)
359                     cap_sizes[cap] += len(s)
360                 else:
361                     output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap))
362             except:
363                 pass
364         elif v.has_key('func'):
365             try:
366                 s = v['func'](cap)
367             except Exception, e:
368                 s = str(e)
369             if unlimited_data or caps[cap][MAX_SIZE] == -1 or \
370                     cap_sizes[cap] < caps[cap][MAX_SIZE]:
371                 v['output'] = StringIOmtime(s)
372                 cap_sizes[cap] += len(s)
373             else:
374                 output("Omitting %s, size constraint of %s exceeded" % (k, cap))
375
376     run_procs(process_lists.values())
377
378
379 def main(argv=None):
380     global ANSWER_YES_TO_ALL, SILENT_MODE
381     global entries, data, dbg, unlimited_data, log_days
382
383     # Filter flags
384     only_ovs_info = False
385     collect_all_info = True
386
387     # we need access to privileged files, exit if we are not running as root
388     if os.getuid() != 0:
389         print >>sys.stderr, "Error: ovs-bugtool must be run as root"
390         return 1
391
392     output_file = None
393     output_type = 'tar.gz'
394     output_fd = -1
395
396     if argv is None:
397         argv = sys.argv
398
399     try:
400         (options, params) = getopt.gnu_getopt(
401             argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
402                          'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
403                          'debug', 'ovs', 'log-days='])
404     except getopt.GetoptError, opterr:
405         print >>sys.stderr, opterr
406         return 2
407
408     try:
409         load_plugins(True)
410     except:
411         pass
412
413     entries = [e for e in caps.keys() if caps[e][CHECKED]]
414
415     for (k, v) in options:
416         if k == '--capabilities':
417             update_capabilities()
418             print_capabilities()
419             return 0
420
421         if k == '--output':
422             if  v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
423                 output_type = v
424             else:
425                 print >>sys.stderr, "Invalid output format '%s'" % v
426                 return 2
427
428         # "-s" or "--silent" means suppress output (except for the final
429         # output filename at the end)
430         if k in ['-s', '--silent']:
431             SILENT_MODE = True
432
433         if k == '--entries' and v != '':
434             entries = v.split(',')
435
436         # If the user runs the script with "-y" or "--yestoall" we don't ask
437         # all the really annoying questions.
438         if k in ['-y', '--yestoall']:
439             ANSWER_YES_TO_ALL = True
440
441         if k == '--outfd':
442             output_fd = int(v)
443             try:
444                 old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
445                 fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
446             except:
447                 print >>sys.stderr, "Invalid output file descriptor", output_fd
448                 return 2
449
450         if k == '--outfile':
451             output_file = v
452
453         elif k == '--all':
454             entries = caps.keys()
455         elif k == '--unlimited':
456             unlimited_data = True
457         elif k == '--debug':
458             dbg = True
459             ProcOutput.debug = True
460
461         if k == '--ovs':
462             only_ovs_info = True
463             collect_all_info = False
464
465         if k == '--log-days':
466             log_days = int(v)
467
468     if len(params) != 1:
469         print >>sys.stderr, "Invalid additional arguments", str(params)
470         return 2
471
472     if output_fd != -1 and output_type != 'tar':
473         print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'"
474         return 2
475
476     if output_fd != -1 and output_file is not None:
477         print >>sys.stderr, "Cannot set both '--outfd' and '--outfile'"
478         return 2
479
480     if ANSWER_YES_TO_ALL:
481         output("Warning: '--yestoall' argument provided, will not prompt for individual files.")
482
483     output('''
484 This application will collate dmesg output, details of the
485 hardware configuration of your machine, information about the build of
486 openvswitch that you are using, plus, if you allow it, various logs.
487
488 The collated information will be saved as a .%s for archiving or
489 sending to a Technical Support Representative.
490
491 The logs may contain private information, and if you are at all
492 worried about that, you should exit now, or you should explicitly
493 exclude those logs from the archive.
494
495 ''' % output_type)
496
497     # assemble potential data
498
499     file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
500     cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
501     cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum')
502
503     cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
504     file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
505     file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
506     cmd_output(CAP_DISK_INFO, [DF, '-alT'])
507     cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
508     if len(pidof('iscsid')) != 0:
509         cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
510     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
511     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
512     cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
513     cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
514     func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
515
516     file_output(CAP_HARDWARE_INFO, [PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
517     cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
518     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
519     cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
520     file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
521     file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
522     cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
523
524
525     file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES, PROC_DEVICES,
526                                   PROC_FILESYSTEMS, PROC_CMDLINE])
527     cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
528     cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
529     file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
530     tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
531     func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
532
533     cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
534
535     file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
536     cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
537     func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
538     cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
539     if CAP_MULTIPATH in entries and collect_all_info:
540         dump_rdac_groups(CAP_MULTIPATH)
541
542     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
543     tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
544     file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF, HOSTS])
545     file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
546     file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_CONF_DB])
547
548     cmd_output(CAP_NETWORK_INFO, [IFCONFIG, '-a'])
549     cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
550     cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
551     cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
552     for dir in DHCP_LEASE_DIR:
553         tree_output(CAP_NETWORK_INFO, dir)
554     for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
555         cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
556     for p in os.listdir('/sys/class/net/'):
557         try:
558             f = open('/sys/class/net/%s/type' % p, 'r')
559             t = f.readline()
560             f.close()
561             if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
562                 # ARPHRD_ETHER
563                 cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
564                 if not p.startswith('vif') and not p.startswith('tap'):
565                     cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
566                     cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
567                     cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
568                     cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
569             if int(t) == 1:
570                 cmd_output(CAP_NETWORK_INFO,
571                            [TC, '-s', '-d', 'class', 'show', 'dev', p])
572         except:
573             pass
574     tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
575     tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
576     cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
577     file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
578     if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
579         cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
580         for d in dp_list():
581             cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', d])
582
583     cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'], label='process-tree')
584     func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
585
586     system_logs = ([ VAR_LOG_DIR + x for x in
587         ['crit.log', 'kern.log', 'daemon.log', 'user.log',
588         'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
589     ovs_logs = ([ OPENVSWITCH_LOG_DIR + x for x in
590         ['ovs-vswitchd.log', 'ovsdb-server.log',
591         'ovs-xapi-sync.log', 'ovs-monitor-ipsec.log', 'ovs-ctl.log']])
592     log_output(CAP_SYSTEM_LOGS, system_logs)
593     log_output(CAP_OPENVSWITCH_LOGS, ovs_logs)
594
595     if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot'):
596         cmd_output(CAP_SYSTEM_LOGS, [DMESG])
597
598     cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
599
600     tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
601
602     file_output(CAP_YUM, [YUM_LOG])
603     tree_output(CAP_YUM, YUM_REPOS_DIR)
604     cmd_output(CAP_YUM, [RPM, '-qa'])
605     file_output(CAP_YUM, [APT_SOURCES_LIST])
606     tree_output(CAP_YUM, APT_SOURCES_LIST_D)
607     cmd_output(CAP_YUM, [DPKG_QUERY, '-W', '-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
608
609     # Filter out ovs relevant information if --ovs option passed
610     # else collect all information
611     filters = set()
612     if only_ovs_info:
613         filters.add('ovs')
614         ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
615                          CAP_NETWORK_CONFIG]
616         ovs_info_list = ['process-tree']
617         # We cannot use iteritems, since we modify 'data' as we pass through
618         for (k, v) in data.items():
619             cap = v['cap']
620             if 'filename' in v:
621                 info = k[0]
622             else:
623                 info = k
624             if info not in ovs_info_list and cap not in ovs_info_caps:
625                 del data[k]
626
627     if filters:
628         filter = ",".join(filters)
629     else:
630         filter = None
631
632     try:
633         load_plugins(filter=filter)
634     except:
635         pass
636
637     # permit the user to filter out data
638     # We cannot use iteritems, since we modify 'data' as we pass through
639     for (k, v) in sorted(data.items()):
640        cap = v['cap']
641        if 'filename' in v:
642            key = k[0]
643        else:
644            key = k
645        if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
646            del data[k]
647
648     # collect selected data now
649     output_ts('Running commands to collect data')
650     collect_data()
651
652     subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
653
654     # include inventory
655     data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))}
656
657     # create archive
658     if output_fd == -1:
659         if output_file is None:
660             dirname = BUG_DIR
661         else:
662             dirname = os.path.dirname(output_file)
663         if dirname and not os.path.exists(dirname):
664             try:
665                 os.makedirs(dirname)
666             except:
667                 pass
668
669     if output_fd == -1:
670         output_ts('Creating output file')
671
672     if output_type.startswith('tar'):
673         make_tar(subdir, output_type, output_fd, output_file)
674     else:
675         make_zip(subdir, output_file)
676
677     if dbg:
678         print >>sys.stderr, "Category sizes (max, actual):\n"
679         for c in caps.keys():
680             print >>sys.stderr, "    %s (%d, %d)" % (c, caps[c][MAX_SIZE],
681                                                      cap_sizes[c])
682     return 0
683
684 def dump_scsi_hosts(cap):
685     output = ''
686     l = os.listdir('/sys/class/scsi_host')
687     l.sort()
688
689     for h in l:
690         procname = ''
691         try:
692                 f = open('/sys/class/scsi_host/%s/proc_name' % h)
693                 procname = f.readline().strip("\n")
694                 f.close()
695         except:
696                 pass
697         modelname = None
698         try:
699                 f = open('/sys/class/scsi_host/%s/model_name' % h)
700                 modelname = f.readline().strip("\n")
701                 f.close()
702         except:
703                 pass
704
705         output += "%s:\n" %h
706         output += "    %s%s\n" % (procname, modelname and (" -> %s" % modelname) or '')
707
708     return output
709
710 def module_info(cap):
711     output = StringIO.StringIO()
712     modules = open(PROC_MODULES, 'r')
713     procs = []
714
715     for line in modules:
716         module = line.split()[0]
717         procs.append(ProcOutput([MODINFO, module], caps[cap][MAX_TIME], output))
718     modules.close()
719
720     run_procs([procs])
721
722     return output.getvalue()
723
724
725 def multipathd_topology(cap):
726     pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
727                      stdout=PIPE, stderr=dev_null)
728     stdout, stderr = pipe.communicate('show topology')
729
730     return stdout
731
732 def dp_list():
733     output = StringIO.StringIO()
734     procs = [ProcOutput([OVS_DPCTL, 'dump-dps'], caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
735
736     run_procs([procs])
737
738     if not procs[0].timed_out:
739         return output.getvalue().splitlines()
740     return []
741
742 def fd_usage(cap):
743     output = ''
744     fd_dict = {}
745     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
746         try:
747             fh = open('/proc/'+d+'/cmdline')
748             name = fh.readline()
749             num_fds = len(os.listdir(os.path.join('/proc/'+d+'/fd')))
750             if num_fds > 0:
751                 if not num_fds in fd_dict:
752                     fd_dict[num_fds] = []
753                 fd_dict[num_fds].append(name.replace('\0', ' ').strip())
754         finally:
755             fh.close()
756     keys = fd_dict.keys()
757     keys.sort(lambda a, b: int(b) - int(a))
758     for k in keys:
759         output += "%s: %s\n" % (k, str(fd_dict[k]))
760     return output
761
762 def dump_rdac_groups(cap):
763     output = StringIO.StringIO()
764     procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
765
766     run_procs([procs])
767
768     if not procs[0].timed_out:
769         proc_line = 0
770         for line in output.getvalue().splitlines():
771             if line.startswith('ID'):
772                 proc_line = 2
773             elif line.startswith('----'):
774                 proc_line -= 1
775             elif proc_line > 0:
776                 group, _ = line.split(None, 1)
777                 cmd_output(cap, [MPPUTIL, '-g', group])
778
779 def load_plugins(just_capabilities=False, filter=None):
780     def getText(nodelist):
781         rc = ""
782         for node in nodelist:
783             if node.nodeType == node.TEXT_NODE:
784                 rc += node.data
785         return rc.encode()
786
787     def getBoolAttr(el, attr, default=False):
788         ret = default
789         val = el.getAttribute(attr).lower()
790         if val in ['true', 'false', 'yes', 'no']:
791             ret = val in ['true', 'yes']
792         return ret
793
794     for dir in [d for d in os.listdir(PLUGIN_DIR) if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
795         if not caps.has_key(dir):
796             if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
797                 continue
798             xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
799             assert xmldoc.documentElement.tagName == "capability"
800
801             pii, min_size, max_size, min_time, max_time, mime = \
802                  PII_MAYBE, -1,-1,-1,-1, MIME_TEXT
803
804             if xmldoc.documentElement.getAttribute("pii") in [PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
805                 pii = xmldoc.documentElement.getAttribute("pii")
806             if xmldoc.documentElement.getAttribute("min_size") != '':
807                 min_size = long(xmldoc.documentElement.getAttribute("min_size"))
808             if xmldoc.documentElement.getAttribute("max_size") != '':
809                 max_size = long(xmldoc.documentElement.getAttribute("max_size"))
810             if xmldoc.documentElement.getAttribute("min_time") != '':
811                 min_time = int(xmldoc.documentElement.getAttribute("min_time"))
812             if xmldoc.documentElement.getAttribute("max_time") != '':
813                 max_time = int(xmldoc.documentElement.getAttribute("max_time"))
814             if xmldoc.documentElement.getAttribute("mime") in [MIME_DATA, MIME_TEXT]:
815                 mime = xmldoc.documentElement.getAttribute("mime")
816             checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
817             hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
818
819             cap(dir, pii, min_size, max_size, min_time, max_time, mime, checked, hidden)
820
821         if just_capabilities:
822             continue
823
824         plugdir = os.path.join(PLUGIN_DIR, dir)
825         for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
826             xmldoc = parse(os.path.join(plugdir, file))
827             assert xmldoc.documentElement.tagName == "collect"
828
829             for el in xmldoc.documentElement.getElementsByTagName("*"):
830                 filters_tmp = el.getAttribute("filters")
831                 if filters_tmp == '':
832                     filters = []
833                 else:
834                     filters = filters_tmp.split(',')
835                 if not(filter is None or filter in filters):
836                     continue
837                 if el.tagName == "files":
838                     newest_first = getBoolAttr(el, 'newest_first')
839                     if el.getAttribute("type") == "logs":
840                         log_output(dir, getText(el.childNodes).split(),
841                                     newest_first=newest_first)
842                     else:
843                         file_output(dir, getText(el.childNodes).split(),
844                                     newest_first=newest_first)
845                 elif el.tagName == "directory":
846                     pattern = el.getAttribute("pattern")
847                     if pattern == '': pattern = None
848                     negate = getBoolAttr(el, 'negate')
849                     newest_first = getBoolAttr(el, 'newest_first')
850                     tree_output(dir, getText(el.childNodes),
851                                 pattern and re.compile(pattern) or None,
852                                 negate=negate, newest_first=newest_first)
853                 elif el.tagName == "command":
854                     label = el.getAttribute("label")
855                     if label == '': label = None
856                     binary = getBoolAttr(el, 'binary')
857                     cmd_output(dir, getText(el.childNodes), label, binary=binary)
858
859 def make_tar(subdir, suffix, output_fd, output_file):
860     global SILENT_MODE, data
861
862     mode = 'w'
863     if suffix == 'tar.bz2':
864         mode = 'w:bz2'
865     elif suffix == 'tar.gz':
866         mode = 'w:gz'
867
868     if output_fd == -1:
869         if output_file is None:
870             filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
871         else:
872             filename = output_file
873         old_umask = os.umask(0077)
874         tf = tarfile.open(filename, mode)
875         os.umask(old_umask)
876     else:
877         tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
878
879     try:
880         for (k, v) in data.items():
881             try:
882                 tar_filename = os.path.join(subdir, construct_filename(k, v))
883                 ti = tarfile.TarInfo(tar_filename)
884
885                 ti.uname = 'root'
886                 ti.gname = 'root'
887
888                 if v.has_key('output'):
889                     ti.mtime = v['output'].mtime
890                     ti.size = len(v['output'].getvalue())
891                     v['output'].seek(0)
892                     tf.addfile(ti, v['output'])
893                 elif v.has_key('filename'):
894                     s = os.stat(v['filename'])
895                     ti.mtime = s.st_mtime
896                     ti.size = s.st_size
897                     tf.addfile(ti, file(v['filename']))
898             except:
899                 pass
900     finally:
901         tf.close()
902
903     if output_fd == -1:
904         output ('Writing tarball %s successful.' % filename)
905         if SILENT_MODE:
906             print filename
907
908
909 def make_zip(subdir, output_file):
910     global SILENT_MODE, data
911
912     if output_file is None:
913         filename = "%s/%s.zip" % (BUG_DIR, subdir)
914     else:
915         filename = output_file
916     old_umask = os.umask(0077)
917     zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
918     os.umask(old_umask)
919
920     try:
921         for (k, v) in data.items():
922             try:
923                 dest = os.path.join(subdir, construct_filename(k, v))
924
925                 if v.has_key('output'):
926                     zf.writestr(dest, v['output'].getvalue())
927                 else:
928                     if os.stat(v['filename']).st_size < 50:
929                         compress_type = zipfile.ZIP_STORED
930                     else:
931                         compress_type = zipfile.ZIP_DEFLATED
932                     zf.write(v['filename'], dest, compress_type)
933             except:
934                 pass
935     finally:
936         zf.close()
937
938     output ('Writing archive %s successful.' % filename)
939     if SILENT_MODE:
940         print filename
941
942
943 def make_inventory(inventory, subdir):
944     document = getDOMImplementation().createDocument(
945         None, INVENTORY_XML_ROOT, None)
946
947     # create summary entry
948     s = document.createElement(INVENTORY_XML_SUMMARY)
949     user = os.getenv('SUDO_USER', os.getenv('USER'))
950     if user:
951         s.setAttribute('user', user)
952     s.setAttribute('date', time.strftime('%c'))
953     s.setAttribute('hostname', platform.node())
954     s.setAttribute('uname', ' '.join(platform.uname()))
955     s.setAttribute('uptime', commands.getoutput(UPTIME))
956     document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
957
958     map(lambda (k, v): inventory_entry(document, subdir, k, v),
959         inventory.items())
960     return document.toprettyxml()
961
962 def inventory_entry(document, subdir, k, v):
963     try:
964         el = document.createElement(INVENTORY_XML_ELEMENT)
965         el.setAttribute('capability', v['cap'])
966         el.setAttribute('filename', os.path.join(subdir, construct_filename(k, v)))
967         el.setAttribute('md5sum', md5sum(v))
968         document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
969     except:
970         pass
971
972
973 def md5sum(d):
974     m = md5.new()
975     if d.has_key('filename'):
976         f = open(d['filename'])
977         data = f.read(1024)
978         while len(data) > 0:
979             m.update(data)
980             data = f.read(1024)
981         f.close()
982     elif d.has_key('output'):
983         m.update(d['output'].getvalue())
984     return m.hexdigest()
985
986
987 def construct_filename(k, v):
988     if v.has_key('filename'):
989         if v['filename'][0] == '/':
990             return v['filename'][1:]
991         else:
992             return v['filename']
993     s = k.replace(' ', '-')
994     s = s.replace('--', '-')
995     s = s.replace('/', '%')
996     if s.find('.') == -1:
997         s += '.out'
998
999     return s
1000
1001 def update_capabilities():
1002     pass
1003
1004 def update_cap_size(cap, size):
1005     update_cap(cap, MIN_SIZE, size)
1006     update_cap(cap, MAX_SIZE, size)
1007     update_cap(cap, CHECKED, size > 0)
1008
1009
1010 def update_cap(cap, k, v):
1011     global caps
1012     l = list(caps[cap])
1013     l[k] = v
1014     caps[cap] = tuple(l)
1015
1016
1017 def size_of_dir(d, pattern=None, negate=False):
1018     if os.path.isdir(d):
1019         return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
1020                            pattern, negate)
1021     else:
1022         return 0
1023
1024
1025 def size_of_all(files, pattern=None, negate=False):
1026     return sum([size_of(f, pattern, negate) for f in files])
1027
1028
1029 def matches(f, pattern, negate):
1030     if negate:
1031         return not matches(f, pattern, False)
1032     else:
1033         return pattern is None or pattern.match(f)
1034
1035
1036 def size_of(f, pattern, negate):
1037     if os.path.isfile(f) and matches(f, pattern, negate):
1038         return os.stat(f)[6]
1039     else:
1040         return size_of_dir(f, pattern, negate)
1041
1042
1043 def print_capabilities():
1044     document = getDOMImplementation().createDocument(
1045         "ns", CAP_XML_ROOT, None)
1046     map(lambda key: capability(document, key), [k for k in caps.keys() if not caps[k][HIDDEN]])
1047     print document.toprettyxml()
1048
1049 def capability(document, key):
1050     c = caps[key]
1051     el = document.createElement(CAP_XML_ELEMENT)
1052     el.setAttribute('key', c[KEY])
1053     el.setAttribute('pii', c[PII])
1054     el.setAttribute('min-size', str(c[MIN_SIZE]))
1055     el.setAttribute('max-size', str(c[MAX_SIZE]))
1056     el.setAttribute('min-time', str(c[MIN_TIME]))
1057     el.setAttribute('max-time', str(c[MAX_TIME]))
1058     el.setAttribute('content-type', c[MIME])
1059     el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
1060     document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
1061
1062
1063 def prettyDict(d):
1064     format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
1065     return '\n'.join([format % i for i in d.items()]) + '\n'
1066
1067
1068 def yes(prompt):
1069     yn = raw_input(prompt)
1070
1071     return len(yn) == 0 or yn.lower()[0] == 'y'
1072
1073
1074 partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
1075
1076 def disk_list():
1077     disks = []
1078     try:
1079         f = open('/proc/partitions')
1080         f.readline()
1081         f.readline()
1082         for line in f.readlines():
1083             (major, minor, blocks, name) = line.split()
1084             if int(major) < 254 and not partition_re.match(name):
1085                 disks.append(name)
1086         f.close()
1087     except:
1088         pass
1089     return disks
1090
1091
1092 class ProcOutput:
1093     debug = False
1094
1095     def __init__(self, command, max_time, inst=None, filter=None, binary=False):
1096         self.command = command
1097         self.max_time = max_time
1098         self.inst = inst
1099         self.running = False
1100         self.status = None
1101         self.timed_out = False
1102         self.failed = False
1103         self.timeout = int(time.time()) + self.max_time
1104         self.filter = filter
1105         self.filter_state = {}
1106         if binary:
1107             self.bufsize = 1048576  # 1MB buffer
1108         else:
1109             self.bufsize = 1        # line buffered
1110
1111     def __del__(self):
1112         self.terminate()
1113
1114     def cmdAsStr(self):
1115         return isinstance(self.command, list) and ' '.join(self.command) or self.command
1116
1117     def run(self):
1118         self.timed_out = False
1119         try:
1120             if ProcOutput.debug:
1121                 output_ts("Starting '%s'" % self.cmdAsStr())
1122             self.proc = Popen(self.command, bufsize=self.bufsize,
1123                               stdin=dev_null, stdout=PIPE, stderr=dev_null,
1124                               shell=isinstance(self.command, str))
1125             old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
1126             fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
1127             self.running = True
1128             self.failed = False
1129         except:
1130             output_ts("'%s' failed" % self.cmdAsStr())
1131             self.running = False
1132             self.failed = True
1133
1134     def terminate(self):
1135         if self.running:
1136             try:
1137                 self.proc.stdout.close()
1138                 os.kill(self.proc.pid, SIGTERM)
1139             except:
1140                 pass
1141             self.proc = None
1142             self.running = False
1143             self.status = SIGTERM
1144
1145     def read_line(self):
1146         assert self.running
1147         if self.bufsize == 1:
1148             line = self.proc.stdout.readline()
1149         else:
1150             line = self.proc.stdout.read(self.bufsize)
1151         if line == '':
1152             # process exited
1153             self.proc.stdout.close()
1154             self.status = self.proc.wait()
1155             self.proc = None
1156             self.running = False
1157         else:
1158             if self.filter:
1159                 line = self.filter(line, self.filter_state)
1160             if self.inst:
1161                 self.inst.write(line)
1162
1163 def run_procs(procs):
1164     while True:
1165         pipes = []
1166         active_procs = []
1167
1168         for pp in procs:
1169             for p in pp:
1170                 if p.running:
1171                     active_procs.append(p)
1172                     pipes.append(p.proc.stdout)
1173                     break
1174                 elif p.status == None and not p.failed and not p.timed_out:
1175                     p.run()
1176                     if p.running:
1177                         active_procs.append(p)
1178                         pipes.append(p.proc.stdout)
1179                         break
1180
1181         if len(pipes) == 0:
1182             # all finished
1183             break
1184
1185         (i, o, x) = select(pipes, [], [], 1.0)
1186         now = int(time.time())
1187
1188         # handle process output
1189         for p in active_procs:
1190             if p.proc.stdout in i:
1191                 p.read_line()
1192
1193             # handle timeout
1194             if p.running and now > p.timeout:
1195                 output_ts("'%s' timed out" % p.cmdAsStr())
1196                 if p.inst:
1197                     p.inst.write("\n** timeout **\n")
1198                 p.timed_out = True
1199                 p.terminate()
1200
1201
1202 def pidof(name):
1203     pids = []
1204
1205     for d in [p for p in os.listdir('/proc') if p.isdigit()]:
1206         try:
1207             if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
1208                 pids.append(int(d))
1209         except:
1210             pass
1211
1212     return pids
1213
1214
1215 class StringIOmtime(StringIO.StringIO):
1216     def __init__(self, buf=''):
1217         StringIO.StringIO.__init__(self, buf)
1218         self.mtime = time.time()
1219
1220     def write(self, s):
1221         StringIO.StringIO.write(self, s)
1222         self.mtime = time.time()
1223
1224
1225 if __name__ == "__main__":
1226     try:
1227         sys.exit(main())
1228     except KeyboardInterrupt:
1229         print "\nInterrupted."
1230         sys.exit(3)