mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 01:51:26 +00:00
README file still mentions a kernel module and some parts of the documentation still have XenServer references, e.g. 'xs-*' database configuration options. Removing them. Fixes: 422e90437854 ("make: Remove the Linux datapath.") Fixes: 83c9518e7c67 ("xenserver: Remove xenserver.") Acked-by: Aaron Conole <aconole@redhat.com> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
1413 lines
45 KiB
Plaintext
Executable File
1413 lines
45 KiB
Plaintext
Executable File
#! @PYTHON3@
|
|
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of version 2.1 of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Copyright (c) 2005, 2007 XenSource Ltd.
|
|
# Copyright (c) 2010, 2011, 2012, 2013, 2015, 2016, 2017 Nicira, Inc.
|
|
|
|
#
|
|
# To add new entries to the bugtool, you need to:
|
|
#
|
|
# Create a new capability. These declare the new entry to the GUI, including
|
|
# the expected size, time to collect, privacy implications, and whether the
|
|
# capability should be selected by default. One capability may refer to
|
|
# multiple files, assuming that they can be reasonably grouped together, and
|
|
# have the same privacy implications. You need:
|
|
#
|
|
# A new CAP_ constant.
|
|
# A cap() invocation to declare the capability.
|
|
#
|
|
# You then need to add calls to main() to collect the files. These will
|
|
# typically be calls to the helpers file_output(), tree_output(), cmd_output(),
|
|
# or func_output().
|
|
#
|
|
|
|
from io import BytesIO
|
|
import fcntl
|
|
import getopt
|
|
import hashlib
|
|
import os
|
|
import platform
|
|
import re
|
|
import sys
|
|
import tarfile
|
|
import time
|
|
import warnings
|
|
import zipfile
|
|
from select import select
|
|
from signal import SIGTERM
|
|
from subprocess import PIPE, Popen, check_output
|
|
|
|
from xml.dom.minidom import getDOMImplementation, parse
|
|
|
|
warnings.filterwarnings(action="ignore", category=DeprecationWarning)
|
|
|
|
OS_RELEASE = platform.release()
|
|
|
|
#
|
|
# Files & directories
|
|
#
|
|
|
|
APT_SOURCES_LIST = "/etc/apt/sources.list"
|
|
APT_SOURCES_LIST_D = "/etc/apt/sources.list.d"
|
|
BUG_DIR = "/var/log/ovs-bugtool"
|
|
PLUGIN_DIR = "@pkgdatadir@/bugtool-plugins"
|
|
GRUB_CONFIG = '/boot/grub/menu.lst'
|
|
BOOT_KERNEL = '/boot/vmlinuz-' + OS_RELEASE
|
|
BOOT_INITRD = '/boot/initrd-' + OS_RELEASE + '.img'
|
|
PROC_PARTITIONS = '/proc/partitions'
|
|
FSTAB = '/etc/fstab'
|
|
PROC_MOUNTS = '/proc/mounts'
|
|
ISCSI_CONF = '/etc/iscsi/iscsid.conf'
|
|
ISCSI_INITIATOR = '/etc/iscsi/initiatorname.iscsi'
|
|
PROC_CPUINFO = '/proc/cpuinfo'
|
|
PROC_MEMINFO = '/proc/meminfo'
|
|
PROC_IOPORTS = '/proc/ioports'
|
|
PROC_INTERRUPTS = '/proc/interrupts'
|
|
PROC_SCSI = '/proc/scsi/scsi'
|
|
PROC_VERSION = '/proc/version'
|
|
PROC_MODULES = '/proc/modules'
|
|
PROC_DEVICES = '/proc/devices'
|
|
PROC_FILESYSTEMS = '/proc/filesystems'
|
|
PROC_CMDLINE = '/proc/cmdline'
|
|
PROC_CONFIG = '/proc/config.gz'
|
|
PROC_USB_DEV = '/proc/bus/usb/devices'
|
|
PROC_NET_BONDING_DIR = '/proc/net/bonding'
|
|
IFCFG_RE = re.compile(r'^.*/ifcfg-.*')
|
|
ROUTE_RE = re.compile(r'^.*/route-.*')
|
|
SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf'
|
|
SYSCONFIG_NETWORK = '/etc/sysconfig/network'
|
|
SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts'
|
|
INTERFACES = '/etc/network/interfaces'
|
|
INTERFACESD = '/etc/network/interfaces.d'
|
|
PROC_NET_VLAN_DIR = '/proc/net/vlan'
|
|
PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat'
|
|
MODPROBE_CONF = '/etc/modprobe.conf'
|
|
MODPROBE_DIR = '/etc/modprobe.d'
|
|
RESOLV_CONF = '/etc/resolv.conf'
|
|
MPP_CONF = '/etc/mpp.conf'
|
|
MULTIPATH_CONF = '/etc/multipath.conf'
|
|
NSSWITCH_CONF = '/etc/nsswitch.conf'
|
|
NTP_CONF = '/etc/ntp.conf'
|
|
IPTABLES_CONFIG = '/etc/sysconfig/iptables-config'
|
|
HOSTS = '/etc/hosts'
|
|
HOSTS_ALLOW = '/etc/hosts.allow'
|
|
HOSTS_DENY = '/etc/hosts.deny'
|
|
DHCP_LEASE_DIR = ['/var/lib/dhclient', '/var/lib/dhcp3']
|
|
OPENVSWITCH_LOG_DIR = '@LOGDIR@/'
|
|
OPENVSWITCH_DEFAULT_SWITCH = '/etc/default/openvswitch-switch' # Debian
|
|
OPENVSWITCH_SYSCONFIG_SWITCH = '/etc/sysconfig/openvswitch' # RHEL
|
|
OPENVSWITCH_CONF_DB = '@DBDIR@/conf.db'
|
|
OPENVSWITCH_COMPACT_DB = '@DBDIR@/bugtool-compact-conf.db'
|
|
OPENVSWITCH_VSWITCHD_PID = '@RUNDIR@/ovs-vswitchd.pid'
|
|
VAR_LOG_DIR = '/var/log/'
|
|
VAR_LOG_CORE_DIR = '/var/log/core'
|
|
YUM_LOG = '/var/log/yum.log'
|
|
YUM_REPOS_DIR = '/etc/yum.repos.d'
|
|
|
|
#
|
|
# External programs
|
|
#
|
|
|
|
os.environ['PATH'] = '/usr/local/sbin:/usr/local/bin:' \
|
|
'/usr/sbin:/usr/bin:/sbin:/bin:@pkgdatadir@/scripts'
|
|
ARP = 'arp'
|
|
CAT = 'cat'
|
|
CHKCONFIG = 'chkconfig'
|
|
DATE = 'date'
|
|
DF = 'df'
|
|
DMESG = 'dmesg'
|
|
DMIDECODE = 'dmidecode'
|
|
DMSETUP = 'dmsetup'
|
|
DPKG_QUERY = 'dpkg-query'
|
|
ETHTOOL = 'ethtool'
|
|
FDISK = 'fdisk'
|
|
FIND = 'find'
|
|
IP = 'ip'
|
|
IPTABLES = 'iptables'
|
|
ISCSIADM = 'iscsiadm'
|
|
LOSETUP = 'losetup'
|
|
LS = 'ls'
|
|
LSPCI = 'lspci'
|
|
MODINFO = 'modinfo'
|
|
MPPUTIL = 'mppUtil'
|
|
MULTIPATHD = 'multipathd'
|
|
NETSTAT = 'netstat'
|
|
OVS_DPCTL = 'ovs-dpctl'
|
|
OVS_OFCTL = 'ovs-ofctl'
|
|
OVS_VSCTL = 'ovs-vsctl'
|
|
PS = 'ps'
|
|
ROUTE = 'route'
|
|
RPM = 'rpm'
|
|
SG_MAP = 'sg_map'
|
|
SHA256_SUM = 'sha256sum'
|
|
SYSCTL = 'sysctl'
|
|
TC = 'tc'
|
|
UPTIME = 'uptime'
|
|
ZCAT = 'zcat'
|
|
|
|
#
|
|
# PII -- Personally identifiable information. Of particular concern are
|
|
# things that would identify customers, or their network topology.
|
|
# Passwords are never to be included in any bug report, regardless of any PII
|
|
# declaration.
|
|
#
|
|
# NO -- No PII will be in these entries.
|
|
# YES -- PII will likely or certainly be in these entries.
|
|
# MAYBE -- The user may wish to audit these entries for PII.
|
|
# IF_CUSTOMIZED -- If the files are unmodified, then they will contain no PII,
|
|
# but since we encourage customers to edit these files, PII may have been
|
|
# introduced by the customer. This is used in particular for the networking
|
|
# scripts in dom0.
|
|
#
|
|
|
|
PII_NO = 'no'
|
|
PII_YES = 'yes'
|
|
PII_MAYBE = 'maybe'
|
|
PII_IF_CUSTOMIZED = 'if_customized'
|
|
KEY = 0
|
|
PII = 1
|
|
MIN_SIZE = 2
|
|
MAX_SIZE = 3
|
|
MIN_TIME = 4
|
|
MAX_TIME = 5
|
|
MIME = 6
|
|
CHECKED = 7
|
|
HIDDEN = 8
|
|
|
|
MIME_DATA = 'application/data'
|
|
MIME_TEXT = 'text/plain'
|
|
|
|
INVENTORY_XML_ROOT = "system-status-inventory"
|
|
INVENTORY_XML_SUMMARY = 'system-summary'
|
|
INVENTORY_XML_ELEMENT = 'inventory-entry'
|
|
CAP_XML_ROOT = "system-status-capabilities"
|
|
CAP_XML_ELEMENT = 'capability'
|
|
|
|
|
|
CAP_BOOT_LOADER = 'boot-loader'
|
|
CAP_DISK_INFO = 'disk-info'
|
|
CAP_HARDWARE_INFO = 'hardware-info'
|
|
CAP_KERNEL_INFO = 'kernel-info'
|
|
CAP_LOSETUP_A = 'loopback-devices'
|
|
CAP_MULTIPATH = 'multipath'
|
|
CAP_NETWORK_CONFIG = 'network-config'
|
|
CAP_NETWORK_INFO = 'network-info'
|
|
CAP_NETWORK_STATUS = 'network-status'
|
|
CAP_OPENVSWITCH_LOGS = 'ovs-system-logs'
|
|
CAP_PROCESS_LIST = 'process-list'
|
|
CAP_SYSTEM_LOGS = 'system-logs'
|
|
CAP_SYSTEM_SERVICES = 'system-services'
|
|
CAP_YUM = 'yum'
|
|
|
|
KB = 1024
|
|
MB = 1024 * 1024
|
|
|
|
caps = {}
|
|
cap_sizes = {}
|
|
unlimited_data = False
|
|
dbg = False
|
|
# Default value for the number of days to collect logs.
|
|
log_days = 20
|
|
log_last_mod_time = None
|
|
free_disk_space = None
|
|
# Default value for delay between repeated commands
|
|
command_delay = 10
|
|
|
|
|
|
def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1,
|
|
max_time=-1, mime=MIME_TEXT, checked=True, hidden=False):
|
|
caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime,
|
|
checked, hidden)
|
|
cap_sizes[key] = 0
|
|
|
|
|
|
cap(CAP_BOOT_LOADER, PII_NO, max_size=3 * KB, max_time=5)
|
|
cap(CAP_DISK_INFO, PII_MAYBE, max_size=50 * KB, max_time=20)
|
|
cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=2 * MB, max_time=20)
|
|
cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=120 * KB, max_time=5)
|
|
cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5)
|
|
cap(CAP_MULTIPATH, PII_MAYBE, max_size=20 * KB, max_time=10)
|
|
cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED, min_size=0, max_size=5 * MB)
|
|
cap(CAP_NETWORK_INFO, PII_YES, max_size=50 * MB, max_time=30)
|
|
cap(CAP_NETWORK_STATUS, PII_YES, max_size=-1, max_time=30)
|
|
cap(CAP_OPENVSWITCH_LOGS, PII_MAYBE, max_size=-1, max_time=5)
|
|
cap(CAP_PROCESS_LIST, PII_YES, max_size=30 * KB, max_time=20)
|
|
cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=200 * MB, max_time=5)
|
|
cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5 * KB, max_time=20)
|
|
cap(CAP_YUM, PII_IF_CUSTOMIZED, max_size=10 * KB, max_time=30)
|
|
|
|
ANSWER_YES_TO_ALL = False
|
|
SILENT_MODE = False
|
|
entries = None
|
|
data = {}
|
|
dev_null = open('/dev/null', 'r+')
|
|
|
|
|
|
def output(x):
|
|
global SILENT_MODE
|
|
if not SILENT_MODE:
|
|
print(x)
|
|
|
|
|
|
def output_ts(x):
|
|
output("[%s] %s" % (time.strftime("%x %X %Z"), x))
|
|
|
|
|
|
def cmd_output(cap, args, label=None, filter=None,
|
|
binary=False, repeat_count=1):
|
|
if cap in entries:
|
|
if not label:
|
|
if isinstance(args, list):
|
|
a = [aa for aa in args]
|
|
a[0] = os.path.basename(a[0])
|
|
label = ' '.join(a)
|
|
else:
|
|
label = args
|
|
data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter,
|
|
'binary': binary, 'repeat_count': repeat_count}
|
|
|
|
|
|
def file_output(cap, path_list, newest_first=False, last_mod_time=None):
|
|
"""
|
|
If newest_first is True, the list of files in path_list is sorted
|
|
by file modification time in descending order, else its sorted
|
|
in ascending order.
|
|
"""
|
|
if cap in entries:
|
|
path_entries = []
|
|
for path in path_list:
|
|
try:
|
|
s = os.stat(path)
|
|
except OSError:
|
|
continue
|
|
if last_mod_time is None or s.st_mtime >= last_mod_time:
|
|
path_entries.append((path, s))
|
|
|
|
def mtime(arg):
|
|
(path, stat) = arg
|
|
return stat.st_mtime
|
|
|
|
path_entries.sort(key=mtime, reverse=newest_first)
|
|
for p in path_entries:
|
|
if check_space(cap, p[0], p[1].st_size):
|
|
data[p] = {'cap': cap, 'filename': p[0]}
|
|
|
|
|
|
def tree_output(cap, path, pattern=None, negate=False, newest_first=False,
|
|
last_mod_time=None):
|
|
"""
|
|
Walks the directory tree rooted at path. Files in current dir are processed
|
|
before files in sub-dirs.
|
|
"""
|
|
if cap in entries:
|
|
if os.path.exists(path):
|
|
for root, dirs, files in os.walk(path):
|
|
fns = [fn for fn in [os.path.join(root, f) for f in files]
|
|
if os.path.isfile(fn) and matches(fn, pattern, negate)]
|
|
file_output(cap, fns, newest_first=newest_first,
|
|
last_mod_time=last_mod_time)
|
|
|
|
|
|
def prefix_output(cap, prefix, newest_first=False, last_mod_time=None):
|
|
"""
|
|
Output files with the same prefix.
|
|
"""
|
|
fns = []
|
|
for root, dirs, files in os.walk(os.path.dirname(prefix)):
|
|
fns += [fn for fn in [os.path.join(root, f) for f in files]
|
|
if fn.startswith(prefix)]
|
|
file_output(cap, fns, newest_first=newest_first,
|
|
last_mod_time=last_mod_time)
|
|
|
|
|
|
def func_output(cap, label, func):
|
|
if cap in entries:
|
|
data[label] = {'cap': cap, 'func': func}
|
|
|
|
|
|
def collect_data():
|
|
first_run = True
|
|
|
|
while True:
|
|
process_lists = {}
|
|
|
|
for (k, v) in data.items():
|
|
cap = v['cap']
|
|
if 'cmd_args' in v:
|
|
if 'output' not in v.keys():
|
|
v['output'] = BytesIOmtime()
|
|
if v['repeat_count'] > 0:
|
|
if cap not in process_lists:
|
|
process_lists[cap] = []
|
|
process_lists[cap].append(
|
|
ProcOutput(v['cmd_args'], caps[cap][MAX_TIME],
|
|
v['output'], v['filter'], v['binary']))
|
|
v['repeat_count'] -= 1
|
|
|
|
if bool(process_lists):
|
|
if not first_run:
|
|
output_ts("Waiting %d sec between repeated commands" %
|
|
command_delay)
|
|
time.sleep(command_delay)
|
|
else:
|
|
first_run = False
|
|
run_procs(process_lists.values())
|
|
else:
|
|
break
|
|
|
|
for (k, v) in data.items():
|
|
cap = v['cap']
|
|
if 'filename' in v and v['filename'].startswith('/proc/'):
|
|
# proc files must be read into memory
|
|
try:
|
|
f = open(v['filename'], 'rb')
|
|
s = f.read()
|
|
f.close()
|
|
if check_space(cap, v['filename'], len(s)):
|
|
v['output'] = BytesIOmtime(s)
|
|
except:
|
|
pass
|
|
elif 'func' in v:
|
|
try:
|
|
s = v['func'](cap)
|
|
except Exception as e:
|
|
s = str(e).encode()
|
|
if check_space(cap, k, len(s)):
|
|
if isinstance(s, str):
|
|
v['output'] = BytesIOmtime(s.encode())
|
|
else:
|
|
v['output'] = BytesIOmtime(s)
|
|
|
|
|
|
def main(argv=None):
|
|
global ANSWER_YES_TO_ALL, SILENT_MODE
|
|
global entries, data, dbg, unlimited_data, free_disk_space
|
|
global log_days, log_last_mod_time, command_delay
|
|
|
|
# Filter flags
|
|
only_ovs_info = False
|
|
collect_all_info = True
|
|
|
|
if '--help' in sys.argv:
|
|
print("""
|
|
%(argv0)s: create status report bundles to assist in problem diagnosis
|
|
usage: %(argv0)s OPTIONS
|
|
|
|
By default, %(argv0)s prompts for permission to collect each form of status
|
|
information and produces a .tar.gz file as output.
|
|
|
|
The following options are available.
|
|
--help display this help message, then exit
|
|
-s, --silent suppress most output to stdout
|
|
|
|
Options for categories of data to collect:
|
|
--entries=CAP_A,CAP_B,... set categories of data to collect
|
|
--all collect all categories
|
|
--ovs collect only directly OVS-related info
|
|
--log-days=DAYS collect DAYS worth of old logs
|
|
--delay=DELAY set delay between repeated command
|
|
-y, --yestoall suppress prompts to confirm collection
|
|
--capabilities print categories as XML on stdout, then exit
|
|
|
|
Output options:
|
|
--output=FORMAT set output format to one of tar tar.bz2 tar.gz zip
|
|
--outfile=FILE write output to FILE
|
|
--outfd=FD write output to FD (requires --output=tar)
|
|
--unlimited ignore default limits on sizes of data collected
|
|
--debug print ovs-bugtool debug info on stdout\
|
|
""" % {'argv0': sys.argv[0]})
|
|
sys.exit(0)
|
|
|
|
# we need access to privileged files, exit if we are not running as root
|
|
if os.getuid() != 0:
|
|
print("Error: ovs-bugtool must be run as root", file=sys.stderr)
|
|
return 1
|
|
|
|
output_file = None
|
|
output_type = 'tar.gz'
|
|
output_fd = -1
|
|
|
|
if argv is None:
|
|
argv = sys.argv
|
|
|
|
try:
|
|
(options, params) = getopt.gnu_getopt(
|
|
argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=',
|
|
'output=', 'outfd=', 'outfile=', 'all', 'unlimited',
|
|
'debug', 'ovs', 'log-days=', 'delay='])
|
|
except getopt.GetoptError as opterr:
|
|
print(opterr, file=sys.stderr)
|
|
return 2
|
|
|
|
try:
|
|
load_plugins(True)
|
|
except:
|
|
pass
|
|
|
|
entries = [e for e in caps.keys() if caps[e][CHECKED]]
|
|
|
|
for (k, v) in options:
|
|
if k == '--capabilities':
|
|
update_capabilities()
|
|
print_capabilities()
|
|
return 0
|
|
|
|
if k == '--output':
|
|
if v in ['tar', 'tar.bz2', 'tar.gz', 'zip']:
|
|
output_type = v
|
|
else:
|
|
print("Invalid output format '%s'" % v, file=sys.stderr)
|
|
return 2
|
|
|
|
# "-s" or "--silent" means suppress output (except for the final
|
|
# output filename at the end)
|
|
if k in ['-s', '--silent']:
|
|
SILENT_MODE = True
|
|
|
|
if k == '--entries' and v != '':
|
|
entries = v.split(',')
|
|
|
|
# If the user runs the script with "-y" or "--yestoall" we don't ask
|
|
# all the really annoying questions.
|
|
if k in ['-y', '--yestoall']:
|
|
ANSWER_YES_TO_ALL = True
|
|
|
|
if k == '--outfd':
|
|
output_fd = int(v)
|
|
try:
|
|
old = fcntl.fcntl(output_fd, fcntl.F_GETFD)
|
|
fcntl.fcntl(output_fd, fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
|
|
except:
|
|
print("Invalid output file descriptor", output_fd,
|
|
file=sys.stderr)
|
|
return 2
|
|
|
|
if k == '--outfile':
|
|
output_file = v
|
|
|
|
elif k == '--all':
|
|
entries = caps.keys()
|
|
elif k == '--unlimited':
|
|
unlimited_data = True
|
|
elif k == '--debug':
|
|
dbg = True
|
|
ProcOutput.debug = True
|
|
|
|
if k == '--ovs':
|
|
only_ovs_info = True
|
|
collect_all_info = False
|
|
|
|
if k == '--log-days':
|
|
log_days = int(v)
|
|
|
|
if k == '--delay':
|
|
command_delay = int(v)
|
|
|
|
if len(params) != 1:
|
|
print("Invalid additional arguments", str(params), file=sys.stderr)
|
|
return 2
|
|
|
|
if output_fd != -1 and output_type != 'tar':
|
|
print("Option '--outfd' only valid with '--output=tar'",
|
|
file=sys.stderr)
|
|
return 2
|
|
|
|
if output_fd != -1 and output_file is not None:
|
|
print("Cannot set both '--outfd' and '--outfile'", file=sys.stderr)
|
|
return 2
|
|
|
|
if output_file is not None and not unlimited_data:
|
|
free_disk_space = get_free_disk_space(output_file) * 90 / 100
|
|
|
|
log_last_mod_time = int(time.time()) - log_days * 86400
|
|
|
|
if ANSWER_YES_TO_ALL:
|
|
output("Warning: '--yestoall' argument provided, will not prompt "
|
|
"for individual files.")
|
|
|
|
output('''
|
|
This application will collate dmesg output, details of the
|
|
hardware configuration of your machine, information about the build of
|
|
openvswitch that you are using, plus, if you allow it, various logs.
|
|
|
|
The collated information will be saved as a .%s for archiving or
|
|
sending to a Technical Support Representative.
|
|
|
|
The logs may contain private information, and if you are at all
|
|
worried about that, you should exit now, or you should explicitly
|
|
exclude those logs from the archive.
|
|
|
|
''' % output_type)
|
|
|
|
# assemble potential data
|
|
|
|
file_output(CAP_BOOT_LOADER, [GRUB_CONFIG])
|
|
cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot'])
|
|
cmd_output(CAP_BOOT_LOADER, [SHA256_SUM, BOOT_KERNEL, BOOT_INITRD],
|
|
label='vmlinuz-initrd.sha256sum')
|
|
|
|
cmd_output(CAP_DISK_INFO, [FDISK, '-l'])
|
|
file_output(CAP_DISK_INFO, [PROC_PARTITIONS, PROC_MOUNTS])
|
|
file_output(CAP_DISK_INFO, [FSTAB, ISCSI_CONF, ISCSI_INITIATOR])
|
|
cmd_output(CAP_DISK_INFO, [DF, '-alT'])
|
|
cmd_output(CAP_DISK_INFO, [DF, '-alTi'])
|
|
if len(pidof('iscsid')) != 0:
|
|
cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node'])
|
|
cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_host'])
|
|
cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/scsi_disk'])
|
|
cmd_output(CAP_DISK_INFO, [LS, '-R', '/sys/class/fc_transport'])
|
|
cmd_output(CAP_DISK_INFO, [SG_MAP, '-x'])
|
|
func_output(CAP_DISK_INFO, 'scsi-hosts', dump_scsi_hosts)
|
|
|
|
file_output(CAP_HARDWARE_INFO,
|
|
[PROC_CPUINFO, PROC_MEMINFO, PROC_IOPORTS, PROC_INTERRUPTS])
|
|
cmd_output(CAP_HARDWARE_INFO, [DMIDECODE])
|
|
cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-n'])
|
|
cmd_output(CAP_HARDWARE_INFO, [LSPCI, '-vv'])
|
|
file_output(CAP_HARDWARE_INFO, [PROC_USB_DEV, PROC_SCSI])
|
|
file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF])
|
|
cmd_output(CAP_HARDWARE_INFO, [LS, '-lR', '/dev'])
|
|
|
|
file_output(CAP_KERNEL_INFO, [PROC_VERSION, PROC_MODULES,
|
|
PROC_DEVICES, PROC_FILESYSTEMS, PROC_CMDLINE])
|
|
cmd_output(CAP_KERNEL_INFO, [ZCAT, PROC_CONFIG], label='config')
|
|
cmd_output(CAP_KERNEL_INFO, [SYSCTL, '-A'])
|
|
file_output(CAP_KERNEL_INFO, [MODPROBE_CONF])
|
|
tree_output(CAP_KERNEL_INFO, MODPROBE_DIR)
|
|
func_output(CAP_KERNEL_INFO, 'modinfo', module_info)
|
|
|
|
cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a'])
|
|
|
|
file_output(CAP_MULTIPATH, [MULTIPATH_CONF, MPP_CONF])
|
|
cmd_output(CAP_MULTIPATH, [DMSETUP, 'table'])
|
|
func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology)
|
|
cmd_output(CAP_MULTIPATH, [MPPUTIL, '-a'])
|
|
if CAP_MULTIPATH in entries and collect_all_info:
|
|
dump_rdac_groups(CAP_MULTIPATH)
|
|
|
|
tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE)
|
|
tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE)
|
|
tree_output(CAP_NETWORK_CONFIG, INTERFACESD)
|
|
file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF,
|
|
NSSWITCH_CONF, HOSTS, INTERFACES])
|
|
file_output(CAP_NETWORK_CONFIG,
|
|
[NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY])
|
|
file_output(CAP_NETWORK_CONFIG, [OPENVSWITCH_DEFAULT_SWITCH,
|
|
OPENVSWITCH_SYSCONFIG_SWITCH])
|
|
|
|
cmd_output(CAP_NETWORK_INFO, [IP, 'addr', 'show'])
|
|
cmd_output(CAP_NETWORK_INFO, [ROUTE, '-n'])
|
|
cmd_output(CAP_NETWORK_INFO, [ARP, '-n'])
|
|
cmd_output(CAP_NETWORK_INFO, [NETSTAT, '-an'])
|
|
for dir in DHCP_LEASE_DIR:
|
|
tree_output(CAP_NETWORK_INFO, dir)
|
|
for table in ['filter', 'nat', 'mangle', 'raw', 'security']:
|
|
cmd_output(CAP_NETWORK_INFO, [IPTABLES, '-t', table, '-nL'])
|
|
for p in os.listdir('/sys/class/net/'):
|
|
try:
|
|
f = open('/sys/class/net/%s/type' % p, 'r')
|
|
t = f.readline()
|
|
f.close()
|
|
if os.path.islink('/sys/class/net/%s/device' % p) and int(t) == 1:
|
|
# ARPHRD_ETHER
|
|
cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-S', p])
|
|
if not p.startswith('vif') and not p.startswith('tap'):
|
|
cmd_output(CAP_NETWORK_INFO, [ETHTOOL, p])
|
|
cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-k', p])
|
|
cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-i', p])
|
|
cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-c', p])
|
|
cmd_output(CAP_NETWORK_INFO, [ETHTOOL, '-l', p])
|
|
if int(t) == 1:
|
|
cmd_output(CAP_NETWORK_INFO,
|
|
[TC, '-s', '-d', 'class', 'show', 'dev', p])
|
|
except:
|
|
pass
|
|
tree_output(CAP_NETWORK_INFO, PROC_NET_BONDING_DIR)
|
|
tree_output(CAP_NETWORK_INFO, PROC_NET_VLAN_DIR)
|
|
cmd_output(CAP_NETWORK_INFO, [TC, '-s', 'qdisc'])
|
|
file_output(CAP_NETWORK_INFO, [PROC_NET_SOFTNET_STAT])
|
|
|
|
collect_ovsdb()
|
|
if os.path.exists(OPENVSWITCH_VSWITCHD_PID):
|
|
cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'show', '-s'])
|
|
for d in dp_list():
|
|
cmd_output(CAP_NETWORK_STATUS, [OVS_DPCTL, 'dump-flows', '-m',
|
|
d.decode()])
|
|
|
|
cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo',
|
|
'pid,tty,stat,time,nice,psr,pcpu,pmem,nwchan,wchan:25,args'],
|
|
label='process-tree')
|
|
func_output(CAP_PROCESS_LIST, 'fd_usage', fd_usage)
|
|
|
|
system_logs = ([VAR_LOG_DIR + x for x in
|
|
['crit.log', 'kern.log', 'daemon.log', 'user.log',
|
|
'syslog', 'messages', 'secure', 'debug', 'dmesg', 'boot']])
|
|
for log in system_logs:
|
|
prefix_output(CAP_SYSTEM_LOGS, log, last_mod_time=log_last_mod_time)
|
|
|
|
ovs_logs = ([OPENVSWITCH_LOG_DIR + x for x in
|
|
['ovs-vswitchd.log', 'ovsdb-server.log', 'ovs-ctl.log']])
|
|
for log in ovs_logs:
|
|
prefix_output(CAP_OPENVSWITCH_LOGS, log,
|
|
last_mod_time=log_last_mod_time)
|
|
|
|
cmd_output(CAP_SYSTEM_LOGS, [DMESG])
|
|
|
|
cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list'])
|
|
|
|
tree_output(CAP_SYSTEM_LOGS, VAR_LOG_CORE_DIR)
|
|
|
|
file_output(CAP_YUM, [YUM_LOG])
|
|
tree_output(CAP_YUM, YUM_REPOS_DIR)
|
|
cmd_output(CAP_YUM, [RPM, '-qa'])
|
|
file_output(CAP_YUM, [APT_SOURCES_LIST])
|
|
tree_output(CAP_YUM, APT_SOURCES_LIST_D)
|
|
cmd_output(CAP_YUM, [DPKG_QUERY, '-W',
|
|
'-f=${Package} ${Version} ${Status}\n'], 'dpkg-packages')
|
|
|
|
# Filter out ovs relevant information if --ovs option passed
|
|
# else collect all information
|
|
filters = set()
|
|
if only_ovs_info:
|
|
filters.add('ovs')
|
|
ovs_info_caps = [CAP_NETWORK_STATUS, CAP_SYSTEM_LOGS,
|
|
CAP_OPENVSWITCH_LOGS, CAP_NETWORK_CONFIG]
|
|
ovs_info_list = ['process-tree']
|
|
# We cannot use items(), since we modify 'data' as we pass through
|
|
for (k, v) in list(data.items()):
|
|
cap = v['cap']
|
|
if 'filename' in v:
|
|
info = k[0]
|
|
else:
|
|
info = k
|
|
if info not in ovs_info_list and cap not in ovs_info_caps:
|
|
del data[k]
|
|
|
|
if filters:
|
|
filter = ",".join(filters)
|
|
else:
|
|
filter = None
|
|
|
|
try:
|
|
load_plugins(filter=filter)
|
|
except:
|
|
pass
|
|
|
|
# permit the user to filter out data
|
|
# We cannot use items(), since we modify 'data' as we pass through
|
|
for (k, v) in list(data.items()):
|
|
cap = v['cap']
|
|
if 'filename' in v:
|
|
key = k[0]
|
|
else:
|
|
key = k
|
|
if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % key):
|
|
del data[k]
|
|
|
|
# collect selected data now
|
|
output_ts('Running commands to collect data')
|
|
collect_data()
|
|
|
|
subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S")
|
|
|
|
# include inventory
|
|
data['inventory.xml'] = {'cap': None,
|
|
'output': BytesIOmtime(make_inventory(data, subdir))}
|
|
|
|
# create archive
|
|
if output_fd == -1:
|
|
if output_file is None:
|
|
dirname = BUG_DIR
|
|
else:
|
|
dirname = os.path.dirname(output_file)
|
|
if dirname and not os.path.exists(dirname):
|
|
try:
|
|
os.makedirs(dirname)
|
|
except:
|
|
pass
|
|
|
|
if output_fd == -1:
|
|
output_ts('Creating output file')
|
|
|
|
if output_type.startswith('tar'):
|
|
make_tar(subdir, output_type, output_fd, output_file)
|
|
else:
|
|
make_zip(subdir, output_file)
|
|
|
|
if dbg:
|
|
print("Category sizes (max, actual):\n", file=sys.stderr)
|
|
for c in caps.keys():
|
|
print(" %s (%d, %d)" % (c, caps[c][MAX_SIZE], cap_sizes[c]),
|
|
file=sys.stderr)
|
|
|
|
cleanup_ovsdb()
|
|
return 0
|
|
|
|
|
|
def dump_scsi_hosts(cap):
|
|
output = ''
|
|
scsi_list = os.listdir('/sys/class/scsi_host')
|
|
scsi_list.sort()
|
|
|
|
for h in scsi_list:
|
|
procname = ''
|
|
try:
|
|
f = open('/sys/class/scsi_host/%s/proc_name' % h)
|
|
procname = f.readline().strip("\n")
|
|
f.close()
|
|
except:
|
|
pass
|
|
modelname = None
|
|
try:
|
|
f = open('/sys/class/scsi_host/%s/model_name' % h)
|
|
modelname = f.readline().strip("\n")
|
|
f.close()
|
|
except:
|
|
pass
|
|
|
|
output += "%s:\n" % h
|
|
output += " %s%s\n" \
|
|
% (procname, modelname and (" -> %s" % modelname) or '')
|
|
|
|
return output
|
|
|
|
|
|
def module_info(cap):
|
|
output = BytesIO()
|
|
modules = open(PROC_MODULES, 'r')
|
|
procs = []
|
|
|
|
for line in modules:
|
|
module = line.split()[0]
|
|
procs.append(ProcOutput([MODINFO, module],
|
|
caps[cap][MAX_TIME], output))
|
|
modules.close()
|
|
|
|
run_procs([procs])
|
|
|
|
return output.getvalue()
|
|
|
|
|
|
def multipathd_topology(cap):
|
|
pipe = Popen([MULTIPATHD, '-k'], bufsize=1, stdin=PIPE,
|
|
stdout=PIPE, stderr=dev_null)
|
|
stdout, stderr = pipe.communicate('show topology')
|
|
|
|
return stdout
|
|
|
|
|
|
def dp_list():
|
|
output = BytesIO()
|
|
procs = [ProcOutput([OVS_DPCTL, 'dump-dps'],
|
|
caps[CAP_NETWORK_STATUS][MAX_TIME], output)]
|
|
|
|
run_procs([procs])
|
|
|
|
if not procs[0].timed_out:
|
|
return output.getvalue().splitlines()
|
|
return []
|
|
|
|
|
|
def collect_ovsdb():
|
|
if not os.path.isfile(OPENVSWITCH_CONF_DB):
|
|
return
|
|
|
|
max_size = 10 * MB
|
|
|
|
try:
|
|
if os.path.getsize(OPENVSWITCH_CONF_DB) > max_size:
|
|
if os.path.isfile(OPENVSWITCH_COMPACT_DB):
|
|
os.unlink(OPENVSWITCH_COMPACT_DB)
|
|
|
|
output = BytesIO()
|
|
max_time = 5
|
|
procs = [ProcOutput(['ovsdb-tool', 'compact',
|
|
OPENVSWITCH_CONF_DB, OPENVSWITCH_COMPACT_DB],
|
|
max_time, output)]
|
|
run_procs([procs])
|
|
file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_COMPACT_DB])
|
|
else:
|
|
file_output(CAP_NETWORK_STATUS, [OPENVSWITCH_CONF_DB])
|
|
except OSError:
|
|
return
|
|
|
|
|
|
def cleanup_ovsdb():
|
|
try:
|
|
if os.path.isfile(OPENVSWITCH_COMPACT_DB):
|
|
os.unlink(OPENVSWITCH_COMPACT_DB)
|
|
except:
|
|
return
|
|
|
|
|
|
def fd_usage(cap):
|
|
output = ''
|
|
fd_dict = {}
|
|
for d in [p for p in os.listdir('/proc') if p.isdigit()]:
|
|
try:
|
|
fh = open('/proc/' + d + '/cmdline')
|
|
name = fh.readline()
|
|
num_fds = len(os.listdir(os.path.join('/proc/' + d + '/fd')))
|
|
if num_fds > 0:
|
|
if num_fds not in fd_dict:
|
|
fd_dict[num_fds] = []
|
|
fd_dict[num_fds].append(name.replace('\0', ' ').strip())
|
|
finally:
|
|
fh.close()
|
|
keys = fd_dict.keys()
|
|
keys.sort(lambda a, b: int(b) - int(a))
|
|
for k in keys:
|
|
output += "%s: %s\n" % (k, str(fd_dict[k]))
|
|
return output
|
|
|
|
|
|
def dump_rdac_groups(cap):
|
|
output = BytesIO()
|
|
procs = [ProcOutput([MPPUTIL, '-a'], caps[cap][MAX_TIME], output)]
|
|
|
|
run_procs([procs])
|
|
|
|
if not procs[0].timed_out:
|
|
proc_line = 0
|
|
for line in output.getvalue().splitlines():
|
|
if line.startswith('ID'):
|
|
proc_line = 2
|
|
elif line.startswith('----'):
|
|
proc_line -= 1
|
|
elif proc_line > 0:
|
|
group, _ = line.split(None, 1)
|
|
cmd_output(cap, [MPPUTIL, '-g', group])
|
|
|
|
|
|
def load_plugins(just_capabilities=False, filter=None):
|
|
global log_last_mod_time
|
|
|
|
def getText(nodelist):
|
|
rc = ""
|
|
for node in nodelist:
|
|
if node.nodeType == node.TEXT_NODE:
|
|
rc += node.data
|
|
return rc
|
|
|
|
def getBoolAttr(el, attr, default=False):
|
|
ret = default
|
|
val = el.getAttribute(attr).lower()
|
|
if val in ['true', 'false', 'yes', 'no']:
|
|
ret = val in ['true', 'yes']
|
|
return ret
|
|
|
|
for dir in [d for d in os.listdir(PLUGIN_DIR)
|
|
if os.path.isdir(os.path.join(PLUGIN_DIR, d))]:
|
|
if dir not in caps:
|
|
if not os.path.exists("%s/%s.xml" % (PLUGIN_DIR, dir)):
|
|
continue
|
|
xmldoc = parse("%s/%s.xml" % (PLUGIN_DIR, dir))
|
|
assert xmldoc.documentElement.tagName == "capability"
|
|
|
|
pii, min_size, max_size, min_time, max_time, mime = \
|
|
PII_MAYBE, -1, -1, -1, -1, MIME_TEXT
|
|
|
|
if xmldoc.documentElement.getAttribute("pii") in \
|
|
[PII_NO, PII_YES, PII_MAYBE, PII_IF_CUSTOMIZED]:
|
|
pii = xmldoc.documentElement.getAttribute("pii")
|
|
if xmldoc.documentElement.getAttribute("min_size") != '':
|
|
min_size = int(
|
|
xmldoc.documentElement.getAttribute("min_size"))
|
|
if xmldoc.documentElement.getAttribute("max_size") != '':
|
|
max_size = int(
|
|
xmldoc.documentElement.getAttribute("max_size"))
|
|
if xmldoc.documentElement.getAttribute("min_time") != '':
|
|
min_time = int(xmldoc.documentElement.getAttribute("min_time"))
|
|
if xmldoc.documentElement.getAttribute("max_time") != '':
|
|
max_time = int(xmldoc.documentElement.getAttribute("max_time"))
|
|
if xmldoc.documentElement.getAttribute("mime") in \
|
|
[MIME_DATA, MIME_TEXT]:
|
|
mime = xmldoc.documentElement.getAttribute("mime")
|
|
checked = getBoolAttr(xmldoc.documentElement, 'checked', True)
|
|
hidden = getBoolAttr(xmldoc.documentElement, 'hidden', False)
|
|
|
|
cap(dir, pii, min_size, max_size, min_time, max_time, mime,
|
|
checked, hidden)
|
|
|
|
if just_capabilities:
|
|
continue
|
|
|
|
plugdir = os.path.join(PLUGIN_DIR, dir)
|
|
for file in [f for f in os.listdir(plugdir) if f.endswith('.xml')]:
|
|
xmldoc = parse(os.path.join(plugdir, file))
|
|
assert xmldoc.documentElement.tagName == "collect"
|
|
|
|
for el in xmldoc.documentElement.getElementsByTagName("*"):
|
|
filters_tmp = el.getAttribute("filters")
|
|
if filters_tmp == '':
|
|
filters = []
|
|
else:
|
|
filters = filters_tmp.split(',')
|
|
if not (filter is None or filter in filters):
|
|
continue
|
|
if el.tagName == "files":
|
|
newest_first = getBoolAttr(el, 'newest_first')
|
|
if el.getAttribute("type") == "logs":
|
|
for fn in getText(el.childNodes).split():
|
|
prefix_output(dir, fn, newest_first=newest_first,
|
|
last_mod_time=log_last_mod_time)
|
|
else:
|
|
file_output(dir, getText(el.childNodes).split(),
|
|
newest_first=newest_first)
|
|
elif el.tagName == "directory":
|
|
pattern = el.getAttribute("pattern")
|
|
if pattern == '':
|
|
pattern = None
|
|
negate = getBoolAttr(el, 'negate')
|
|
newest_first = getBoolAttr(el, 'newest_first')
|
|
if el.getAttribute("type") == "logs":
|
|
tree_output(dir, getText(el.childNodes),
|
|
pattern and re.compile(pattern) or None,
|
|
negate=negate, newest_first=newest_first,
|
|
last_mod_time=log_last_mod_time)
|
|
else:
|
|
tree_output(dir, getText(el.childNodes),
|
|
pattern and re.compile(pattern) or None,
|
|
negate=negate, newest_first=newest_first)
|
|
elif el.tagName == "command":
|
|
label = el.getAttribute("label")
|
|
if label == '':
|
|
label = None
|
|
binary = getBoolAttr(el, 'binary')
|
|
try:
|
|
repeat_count = int(el.getAttribute("repeat"))
|
|
if repeat_count > 1:
|
|
cmd_output(dir,
|
|
DATE + ';' + getText(el.childNodes),
|
|
label, binary=binary,
|
|
repeat_count=repeat_count)
|
|
else:
|
|
cmd_output(dir, getText(el.childNodes),
|
|
label, binary=binary,
|
|
repeat_count=repeat_count)
|
|
except:
|
|
cmd_output(dir, getText(el.childNodes), label,
|
|
binary=binary)
|
|
|
|
|
|
def make_tar(subdir, suffix, output_fd, output_file):
|
|
global SILENT_MODE, data
|
|
|
|
mode = 'w'
|
|
if suffix == 'tar.bz2':
|
|
mode = 'w:bz2'
|
|
elif suffix == 'tar.gz':
|
|
mode = 'w:gz'
|
|
|
|
if output_fd == -1:
|
|
if output_file is None:
|
|
filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix)
|
|
else:
|
|
filename = output_file
|
|
old_umask = os.umask(0o077)
|
|
tf = tarfile.open(filename, mode)
|
|
os.umask(old_umask)
|
|
else:
|
|
tf = tarfile.open(None, 'w', os.fdopen(output_fd, 'a'))
|
|
|
|
try:
|
|
for (k, v) in data.items():
|
|
try:
|
|
tar_filename = os.path.join(subdir, construct_filename(k, v))
|
|
ti = tarfile.TarInfo(tar_filename)
|
|
|
|
ti.uname = 'root'
|
|
ti.gname = 'root'
|
|
|
|
if 'output' in v:
|
|
ti.mtime = v['output'].mtime
|
|
ti.size = len(v['output'].getvalue())
|
|
v['output'].seek(0)
|
|
tf.addfile(ti, v['output'])
|
|
elif 'filename' in v:
|
|
s = os.stat(v['filename'])
|
|
ti.mtime = s.st_mtime
|
|
ti.size = s.st_size
|
|
tf.addfile(ti, open(v['filename'], 'rb'))
|
|
except:
|
|
pass
|
|
finally:
|
|
tf.close()
|
|
|
|
if output_fd == -1:
|
|
output('Writing tarball %s successful.' % filename)
|
|
if SILENT_MODE:
|
|
print(filename)
|
|
|
|
|
|
def make_zip(subdir, output_file):
|
|
global SILENT_MODE, data
|
|
|
|
if output_file is None:
|
|
filename = "%s/%s.zip" % (BUG_DIR, subdir)
|
|
else:
|
|
filename = output_file
|
|
old_umask = os.umask(0o077)
|
|
zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED)
|
|
os.umask(old_umask)
|
|
|
|
try:
|
|
for (k, v) in data.items():
|
|
try:
|
|
dest = os.path.join(subdir, construct_filename(k, v))
|
|
|
|
if 'output' in v:
|
|
zf.writestr(dest, v['output'].getvalue())
|
|
else:
|
|
if os.stat(v['filename']).st_size < 50:
|
|
compress_type = zipfile.ZIP_STORED
|
|
else:
|
|
compress_type = zipfile.ZIP_DEFLATED
|
|
zf.write(v['filename'], dest, compress_type)
|
|
except:
|
|
pass
|
|
finally:
|
|
zf.close()
|
|
|
|
output('Writing archive %s successful.' % filename)
|
|
if SILENT_MODE:
|
|
print(filename)
|
|
|
|
|
|
def make_inventory(inventory, subdir):
|
|
document = getDOMImplementation().createDocument(
|
|
None, INVENTORY_XML_ROOT, None)
|
|
|
|
# create summary entry
|
|
s = document.createElement(INVENTORY_XML_SUMMARY)
|
|
user = os.getenv('SUDO_USER', os.getenv('USER'))
|
|
if user:
|
|
s.setAttribute('user', user)
|
|
s.setAttribute('date', time.strftime('%c'))
|
|
s.setAttribute('hostname', platform.node())
|
|
s.setAttribute('uname', ' '.join(platform.uname()))
|
|
s.setAttribute('uptime', check_output(UPTIME).decode())
|
|
document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s)
|
|
|
|
map(lambda k_v: inventory_entry(document, subdir, k_v[0], k_v[1]),
|
|
inventory.items())
|
|
return document.toprettyxml().encode()
|
|
|
|
|
|
def inventory_entry(document, subdir, k, v):
|
|
try:
|
|
el = document.createElement(INVENTORY_XML_ELEMENT)
|
|
el.setAttribute('capability', v['cap'])
|
|
el.setAttribute('filename',
|
|
os.path.join(subdir, construct_filename(k, v)))
|
|
el.setAttribute('sha256sum', sha256(v))
|
|
document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el)
|
|
except:
|
|
pass
|
|
|
|
|
|
def sha256(d):
|
|
m = hashlib.sha256()
|
|
if 'filename' in d:
|
|
f = open(d['filename'])
|
|
data = f.read(1024)
|
|
while len(data) > 0:
|
|
m.update(data)
|
|
data = f.read(1024)
|
|
f.close()
|
|
elif 'output' in d:
|
|
m.update(d['output'].getvalue())
|
|
return m.hexdigest()
|
|
|
|
|
|
def construct_filename(k, v):
|
|
if 'filename' in v:
|
|
if v['filename'][0] == '/':
|
|
return v['filename'][1:]
|
|
else:
|
|
return v['filename']
|
|
s = k.replace(' ', '-')
|
|
s = s.replace('--', '-')
|
|
s = s.replace('/', '%')
|
|
if s.find('.') == -1:
|
|
s += '.out'
|
|
|
|
return s
|
|
|
|
|
|
def update_capabilities():
|
|
pass
|
|
|
|
|
|
def update_cap_size(cap, size):
|
|
update_cap(cap, MIN_SIZE, size)
|
|
update_cap(cap, MAX_SIZE, size)
|
|
update_cap(cap, CHECKED, size > 0)
|
|
|
|
|
|
def update_cap(cap, k, v):
|
|
global caps
|
|
temp = list(caps[cap])
|
|
temp[k] = v
|
|
caps[cap] = tuple(temp)
|
|
|
|
|
|
def size_of_dir(d, pattern=None, negate=False):
|
|
if os.path.isdir(d):
|
|
return size_of_all([os.path.join(d, fn) for fn in os.listdir(d)],
|
|
pattern, negate)
|
|
else:
|
|
return 0
|
|
|
|
|
|
def size_of_all(files, pattern=None, negate=False):
|
|
return sum([size_of(f, pattern, negate) for f in files])
|
|
|
|
|
|
def matches(f, pattern, negate):
|
|
if negate:
|
|
return not matches(f, pattern, False)
|
|
else:
|
|
return pattern is None or pattern.match(f)
|
|
|
|
|
|
def size_of(f, pattern, negate):
|
|
if os.path.isfile(f) and matches(f, pattern, negate):
|
|
return os.stat(f)[6]
|
|
else:
|
|
return size_of_dir(f, pattern, negate)
|
|
|
|
|
|
def print_capabilities():
|
|
document = getDOMImplementation().createDocument(
|
|
"ns", CAP_XML_ROOT, None)
|
|
map(lambda key: capability(document, key),
|
|
[k for k in caps.keys() if not caps[k][HIDDEN]])
|
|
print(document.toprettyxml())
|
|
|
|
|
|
def capability(document, key):
|
|
c = caps[key]
|
|
el = document.createElement(CAP_XML_ELEMENT)
|
|
el.setAttribute('key', c[KEY])
|
|
el.setAttribute('pii', c[PII])
|
|
el.setAttribute('min-size', str(c[MIN_SIZE]))
|
|
el.setAttribute('max-size', str(c[MAX_SIZE]))
|
|
el.setAttribute('min-time', str(c[MIN_TIME]))
|
|
el.setAttribute('max-time', str(c[MAX_TIME]))
|
|
el.setAttribute('content-type', c[MIME])
|
|
el.setAttribute('default-checked', c[CHECKED] and 'yes' or 'no')
|
|
document.getElementsByTagName(CAP_XML_ROOT)[0].appendChild(el)
|
|
|
|
|
|
def prettyDict(d):
|
|
format = '%%-%ds: %%s' % max(map(len, [k for k, _ in d.items()]))
|
|
return '\n'.join([format % i for i in d.items()]) + '\n'
|
|
|
|
|
|
def yes(prompt):
|
|
yn = input(prompt)
|
|
|
|
return len(yn) == 0 or yn.lower()[0] == 'y'
|
|
|
|
|
|
partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)')
|
|
|
|
|
|
def disk_list():
|
|
disks = []
|
|
try:
|
|
f = open('/proc/partitions')
|
|
f.readline()
|
|
f.readline()
|
|
for line in f.readlines():
|
|
(major, minor, blocks, name) = line.split()
|
|
if int(major) < 254 and not partition_re.match(name):
|
|
disks.append(name)
|
|
f.close()
|
|
except:
|
|
pass
|
|
return disks
|
|
|
|
|
|
class ProcOutput(object):
|
|
debug = False
|
|
|
|
def __init__(self, command, max_time, inst=None, filter=None,
|
|
binary=False):
|
|
self.command = command
|
|
self.max_time = max_time
|
|
self.inst = inst
|
|
self.running = False
|
|
self.status = None
|
|
self.timed_out = False
|
|
self.failed = False
|
|
self.timeout = int(time.time()) + self.max_time
|
|
self.filter = filter
|
|
self.filter_state = {}
|
|
if binary:
|
|
self.bufsize = 1048576 # 1MB buffer
|
|
else:
|
|
self.bufsize = 1 # line buffered
|
|
|
|
def __del__(self):
|
|
self.terminate()
|
|
|
|
def cmdAsStr(self):
|
|
return isinstance(self.command, list) \
|
|
and ' '.join(self.command) or self.command
|
|
|
|
def run(self):
|
|
self.timed_out = False
|
|
try:
|
|
if ProcOutput.debug:
|
|
output_ts("Starting '%s'" % self.cmdAsStr())
|
|
self.proc = Popen(self.command, bufsize=self.bufsize,
|
|
stdin=dev_null, stdout=PIPE, stderr=dev_null,
|
|
shell=isinstance(self.command, str))
|
|
old = fcntl.fcntl(self.proc.stdout.fileno(), fcntl.F_GETFD)
|
|
fcntl.fcntl(self.proc.stdout.fileno(),
|
|
fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
|
|
self.running = True
|
|
self.failed = False
|
|
except:
|
|
output_ts("'%s' failed" % self.cmdAsStr())
|
|
self.running = False
|
|
self.failed = True
|
|
|
|
def terminate(self):
|
|
if self.running:
|
|
try:
|
|
self.proc.stdout.close()
|
|
os.kill(self.proc.pid, SIGTERM)
|
|
except:
|
|
pass
|
|
self.proc = None
|
|
self.running = False
|
|
self.status = SIGTERM
|
|
|
|
def read_line(self):
|
|
assert self.running
|
|
if self.bufsize == 1:
|
|
line = self.proc.stdout.readline()
|
|
else:
|
|
line = self.proc.stdout.read(self.bufsize)
|
|
if line == b'':
|
|
# process exited
|
|
self.proc.stdout.close()
|
|
self.status = self.proc.wait()
|
|
self.proc = None
|
|
self.running = False
|
|
else:
|
|
if self.filter:
|
|
line = self.filter(line, self.filter_state)
|
|
if self.inst:
|
|
self.inst.write(line)
|
|
|
|
|
|
def run_procs(procs):
|
|
while True:
|
|
pipes = []
|
|
active_procs = []
|
|
|
|
for pp in procs:
|
|
for p in pp:
|
|
if p.running:
|
|
active_procs.append(p)
|
|
pipes.append(p.proc.stdout)
|
|
break
|
|
elif p.status is None and not p.failed and not p.timed_out:
|
|
p.run()
|
|
if p.running:
|
|
active_procs.append(p)
|
|
pipes.append(p.proc.stdout)
|
|
break
|
|
|
|
if len(pipes) == 0:
|
|
# all finished
|
|
break
|
|
|
|
(i, o, x) = select(pipes, [], [], 1.0)
|
|
now = int(time.time())
|
|
|
|
# handle process output
|
|
for p in active_procs:
|
|
if p.proc.stdout in i:
|
|
p.read_line()
|
|
|
|
# handle timeout
|
|
if p.running and now > p.timeout:
|
|
output_ts("'%s' timed out" % p.cmdAsStr())
|
|
if p.inst:
|
|
p.inst.write("\n** timeout **\n".encode())
|
|
p.timed_out = True
|
|
p.terminate()
|
|
|
|
|
|
def pidof(name):
|
|
pids = []
|
|
|
|
for d in [p for p in os.listdir('/proc') if p.isdigit()]:
|
|
try:
|
|
if os.path.basename(os.readlink('/proc/%s/exe' % d)) == name:
|
|
pids.append(int(d))
|
|
except:
|
|
pass
|
|
|
|
return pids
|
|
|
|
|
|
def check_space(cap, name, size):
|
|
global free_disk_space
|
|
if free_disk_space is not None and size > free_disk_space:
|
|
output("Omitting %s, out of disk space (requested: %u, allowed: %u)" %
|
|
(name, size, free_disk_space))
|
|
return False
|
|
elif unlimited_data or caps[cap][MAX_SIZE] == -1 or \
|
|
cap_sizes[cap] < caps[cap][MAX_SIZE]:
|
|
cap_sizes[cap] += size
|
|
if free_disk_space is not None:
|
|
free_disk_space -= size
|
|
return True
|
|
else:
|
|
output("Omitting %s, size constraint of %s exceeded" % (name, cap))
|
|
return False
|
|
|
|
|
|
def get_free_disk_space(path):
|
|
path = os.path.abspath(path)
|
|
while not os.path.exists(path):
|
|
path = os.path.dirname(path)
|
|
s = os.statvfs(path)
|
|
return s.f_frsize * s.f_bfree
|
|
|
|
|
|
class BytesIOmtime(BytesIO):
|
|
def __init__(self, buf=b''):
|
|
BytesIO.__init__(self, buf)
|
|
self.mtime = time.time()
|
|
|
|
def write(self, s):
|
|
BytesIO.write(self, s)
|
|
self.mtime = time.time()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except KeyboardInterrupt:
|
|
print("\nInterrupted.")
|
|
sys.exit(3)
|