From 16cd277e74b37072301c395c0067d1a17184e533 Mon Sep 17 00:00:00 2001 From: Justin Pettit Date: Wed, 5 Aug 2009 16:02:19 -0700 Subject: [PATCH] XenServer: Add knowledge of vswitch to xen-bugtool The xen-bugtool tool gathers information that may be useful in debugging problems. This commit modifies that script to add support for vswitch. Currently, the information we collect is: - /etc/ovs-vswitchd.conf - ovs-dpctl show - Cores in /var/xen/vswitch - ovs-ofctl show - ovs-ofctl status - ovs-ofctl dump-flows - ovs-dpctl dump-flows This commit also modifies the way cores are handled. They are now enabled by default and placed in "/var/xen/vswitch". Feature #1570 --- xenserver/README | 4 + xenserver/automake.mk | 1 + xenserver/etc_init.d_vswitch | 27 +- xenserver/etc_sysconfig_vswitch.example | 10 +- xenserver/usr_sbin_xen-bugtool | 1451 +++++++++++++++++++++++ xenserver/vswitch-xen.spec | 10 +- 6 files changed, 1490 insertions(+), 13 deletions(-) create mode 100755 xenserver/usr_sbin_xen-bugtool diff --git a/xenserver/README b/xenserver/README index 234483520..6c91e9413 100644 --- a/xenserver/README +++ b/xenserver/README @@ -60,6 +60,10 @@ files are: used to control vswitch when integrated with Citrix management tools. + usr_sbin_xen-bugtool + + vswitch-aware replacement for Citrix script of the same name. + vswitch-xen.spec spec file for building RPMs to install on a XenServer host. diff --git a/xenserver/automake.mk b/xenserver/automake.mk index 225b87072..2fe1289c6 100644 --- a/xenserver/automake.mk +++ b/xenserver/automake.mk @@ -17,4 +17,5 @@ EXTRA_DIST += \ xenserver/opt_xensource_libexec_interface-reconfigure \ xenserver/root_vswitch_scripts_dump-vif-details \ xenserver/usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py \ + xenserver/usr_sbin_xen-bugtool \ xenserver/vswitch-xen.spec diff --git a/xenserver/etc_init.d_vswitch b/xenserver/etc_init.d_vswitch index 3927223c2..90b077731 100755 --- a/xenserver/etc_init.d_vswitch +++ b/xenserver/etc_init.d_vswitch @@ -27,12 +27,12 @@ test -e /etc/sysconfig/vswitch && . /etc/sysconfig/vswitch VSWITCH_BASE="${VSWITCH_BASE:-/root/vswitch}" ENABLE_BRCOMPAT="${ENABLE_BRCOMPAT:-y}" ENABLE_FAKE_PROC_NET="${ENABLE_FAKE_PROC_NET:-y}" -FORCE_COREFILES="${FORCE_COREFILES:-n}" -COREFILE_PATTERN="${COREFILE_PATTERN:-/var/log/%e-%t}" +FORCE_COREFILES="${FORCE_COREFILES:-y}" # Config variables specific to ovs-vswitchd VSWITCHD_CONF="${VSWITCHD_CONF:-/etc/ovs-vswitchd.conf}" VSWITCHD_PIDFILE="${VSWITCHD_PIDFILE:-/var/run/ovs-vswitchd.pid}" +VSWITCHD_RUN_DIR="${VSWITCHD_RUN_DIR:-/var/xen/vswitch}" VSWITCHD_PRIORITY="${VSWITCHD_PRIORITY:--5}" VSWITCHD_LOGFILE="${VSWITCHD_LOGFILE:-/var/log/ovs-vswitchd.log}" VSWITCHD_FILE_LOGLEVEL="${VSWITCHD_FILE_LOGLEVEL:-}" @@ -45,6 +45,7 @@ VSWITCHD_VALGRIND_OPT="${VSWITCHD_VALGRIND_OPT:-}" # Config variables specific to ovs-brcompatd BRCOMPATD_PIDFILE="${BRCOMPATD_PIDFILE:-/var/run/ovs-brcompatd.pid}" +BRCOMPATD_RUN_DIR="${BRCOMPATD_RUN_DIR:-/var/xen/vswitch}" BRCOMPATD_PRIORITY="${BRCOMPATD_PRIORITY:--5}" BRCOMPATD_LOGFILE="${BRCOMPATD_LOGFILE:-/var/log/ovs-brcompatd.log}" BRCOMPATD_FILE_LOGLEVEL="${BRCOMPATD_FILE_LOGLEVEL:-}" @@ -78,9 +79,7 @@ function dp_list { } function turn_on_corefiles { - # This has global effect so should not normally be used... - ulimit -c unlimited - echo "$COREFILE_PATTERN" > /proc/sys/kernel/core_pattern + ulimit -Sc 67108864 } function remove_all_dp { @@ -120,6 +119,10 @@ function start_vswitchd { local syslog_opt="-vANY:SYSLOG:${VSWITCHD_SYSLOG_LOGLEVEL}" local logfile_file_opt="" local logfile_level_opt="" + if [ ! -d "$VSWITCHD_RUN_DIR" ]; then + mkdir -p "$VSWITCHD_RUN_DIR" + fi + cd "$VSWITCHD_RUN_DIR" if [ -n "$VSWITCHD_FILE_LOGLEVEL" ]; then logfile_level_opt="-vANY:FILE:${VSWITCHD_FILE_LOGLEVEL}" logfile_file_opt="--log-file=$VSWITCHD_LOGFILE" @@ -152,9 +155,9 @@ function start_vswitchd { if [ "$daemonize" != "y" ]; then # Start in background and force a "success" message action "Starting ovs-vswitchd ($strace_opt$valgrind_opt)" true - (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") & + (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") & else - action "Starting ovs-vswitchd" nice -n "$VSWITCHD_PRIORITY" "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF" + action "Starting ovs-vswitchd" nice -n "$VSWITCHD_PRIORITY" "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF" fi } @@ -162,7 +165,11 @@ function start_brcompatd { local syslog_opt="-vANY:SYSLOG:${BRCOMPATD_SYSLOG_LOGLEVEL}" local logfile_file_opt="" local logfile_level_opt="" - if [ -n "$BRCOMPATD_FILE_LOGLEVEL" ]; then + if [ -d "$BRCOMPATD_RUN_DIR" ]; then + mkdir -p "$BRCOMPATD_RUN_DIR" + fi + cd "$BRCOMPATD_RUN_DIR" + if [ ! -n "$BRCOMPATD_FILE_LOGLEVEL" ]; then logfile_level_opt="-vANY:FILE:${BRCOMPATD_FILE_LOGLEVEL}" logfile_file_opt="--log-file=$BRCOMPATD_LOGFILE" fi @@ -191,9 +198,9 @@ function start_brcompatd { if [ "$daemonize" != "y" ]; then # Start in background and force a "success" message action "Starting ovs-brcompatd ($strace_opt$valgrind_opt)" true - (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") & + (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd"--no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") & else - action "Starting ovs-brcompatd" nice -n "$BRCOMPATD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE --detach -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF" + action "Starting ovs-brcompatd" nice -n "$BRCOMPATD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE --detach -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF" fi } diff --git a/xenserver/etc_sysconfig_vswitch.example b/xenserver/etc_sysconfig_vswitch.example index cd13b5915..789d61ab1 100644 --- a/xenserver/etc_sysconfig_vswitch.example +++ b/xenserver/etc_sysconfig_vswitch.example @@ -21,7 +21,7 @@ # ENABLE_FAKE_PROC_NET=y # FORCE_COREFILES: If 'y' then core files will be enabled. -# FORCE_COREFILES=n +# FORCE_COREFILES=y # COREFILE_PATTERN: Pattern used to determine path and filename for # core files when FORCE_COREFILES is 'y'. This is Linux specific. @@ -35,6 +35,10 @@ # ovs-vswitchd. # VSWITCHD_PIDFILE=/var/run/ovs-vswitchd.pid +# VSWITCHD_RUN_DIR: Set the directory in which ovs-vswitchd should be +# run. This mainly affects where core files will be placed. +# VSWITCHD_RUN_DIR=/var/xen/vswitch + # VSWITCHD_PRIORITY: "nice" priority at which to run ovs-vswitchd and related # processes. # VSWITCHD_PRIORITY=-5 @@ -60,6 +64,10 @@ # the default is to use brcompat! # BRCOMPATD_PIDFILE=/var/run/ovs-brcompatd.pid +# BRCOMPATD_RUN_DIR: Set the directory in which ovs-brcompatd should be +# run. This mainly affects where core files will be placed. +# BRCOMPATD_RUN_DIR=/var/xen/vswitch + # BRCOMPATD_PRIORITY: "nice" priority at which to run ovs-vswitchd and related # processes. # BRCOMPATD_PRIORITY=-5 diff --git a/xenserver/usr_sbin_xen-bugtool b/xenserver/usr_sbin_xen-bugtool new file mode 100755 index 000000000..0c976010d --- /dev/null +++ b/xenserver/usr_sbin_xen-bugtool @@ -0,0 +1,1451 @@ +#!/usr/bin/env python + +# 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. + + +# +# 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(). +# + +import getopt +import re +import os +import StringIO +import sys +import tarfile +import time +import commands +import pprint +from xml.dom.minidom import parse, getDOMImplementation +import zipfile +from subprocess import Popen, PIPE +from select import select +from signal import SIGTERM, SIGUSR1 +import md5 +import platform +import fcntl +import glob +import urllib +import socket +import base64 + +sys.path.append('/usr/lib/python') +sys.path.append('/usr/lib64/python') + +import xen.lowlevel.xc +import XenAPI + +OS_RELEASE = platform.release() + +# +# Files & directories +# + +BUG_DIR = "/var/opt/xen/bug-report" +XAPI_BLOBS = '/var/xapi/blobs' +EXTLINUX_CONFIG = '/boot/extlinux.conf' +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' +LVM_CACHE = '/etc/lvm/.cache' +PROC_CPUINFO = '/proc/cpuinfo' +PROC_MEMINFO = '/proc/meminfo' +PROC_IOPORTS = '/proc/ioports' +PROC_INTERRUPTS = '/proc/interrupts' +PROC_SCSI = '/proc/scsi/scsi' +FIRSTBOOT_DIR = '/etc/firstboot.d' +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_XEN_BALLOON = '/proc/xen/balloon' +PROC_NET_BONDING_DIR = '/proc/net/bonding' +PROC_NET_VLAN_DIR = '/proc/net/vlan' +PROC_NET_SOFTNET_STAT = '/proc/net/softnet_stat' +PROC_DRIVER_CCISS_DIR = '/proc/driver/cciss' +MODPROBE_CONF = '/etc/modprobe.conf' +MODPROBE_DIR = '/etc/modprobe.d' +BOOT_TIME_CPUS = '/etc/xensource/boot_time_cpus' +BOOT_TIME_MEMORY = '/etc/xensource/boot_time_memory' +SYSCONFIG_HWCONF = '/etc/sysconfig/hwconf' +SYSCONFIG_NETWORK = '/etc/sysconfig/network' +SYSCONFIG_NETWORK_SCRIPTS = '/etc/sysconfig/network-scripts' +IFCFG_RE = re.compile(r'^.*/ifcfg-.*') +ROUTE_RE = re.compile(r'^.*/route-.*') +RESOLV_CONF = '/etc/resolv.conf' +MULTIPATH_CONF = '/etc/multipath.conf' +NSSWITCH_CONF = '/etc/nsswitch.conf' +NTP_CONF = '/etc/ntp.conf' +IPTABLES_CONFIG = '/etc/sysconfig/iptables-config' +HOSTS_ALLOW = '/etc/hosts.allow' +HOSTS_DENY = '/etc/hosts.deny' +DHCP_LEASE_DIR = '/var/lib/dhclient' +DELL_OMSA_LOGS = '/var/log/dell' +HP_CMA_LOG = '/var/spool/compaq/cma.log' +HP_HPASMD_LOG = '/var/spool/compaq/hpasmd.log' +VAR_LOG_DIR = '/var/log/' +VNCTERM_CORE_DIR = '/var/xen/vncterm' +VSWITCH_CORE_DIR = '/var/xen/vswitch' +OVS_VSWITCH_CONF = '/etc/ovs-vswitchd.conf' +XENSOURCE_INVENTORY = '/etc/xensource-inventory' +OEM_CONFIG_DIR = '/var/xsconfig' +OEM_CONFIG_FILES_RE = re.compile(r'^.*xensource-inventory$') +OEM_DB_FILES_RE = re.compile(r'^.*state\.db') +INITIAL_INVENTORY = '/opt/xensource/etc/initial-inventory' +VENDORKERNEL_INVENTORY = '/etc/vendorkernel-inventory' +STATIC_VDIS = '/etc/xensource/static-vdis' +POOL_CONF = '/etc/xensource/pool.conf' +PTOKEN = '/etc/xensource/ptoken' +XAPI_CONF = '/etc/xensource/xapi.conf' +XAPI_SSL_CONF = '/etc/xensource/xapi-ssl.conf' +DB_CONF = '/etc/xensource/db.conf' +DB_CONF_RIO = '/etc/xensource/db.conf.rio' +DB_DEFAULT_FIELDS = '/etc/xensource/db-default-fields' +DB_SCHEMA_SQL = '/etc/xensource/db_schema.sql' +XENSTORED_DB = '/var/lib/xenstored/tdb' +HOST_CRASHDUMPS_DIR = '/var/crash' +HOST_CRASHDUMP_LOGS_RE = re.compile(r'^.*\.log$') +X11_LOGS_DIR = VAR_LOG_DIR +X11_LOGS_RE = re.compile(r'.*/Xorg\..*$') +X11_AUTH_DIR = '/root/' +X11_AUTH_RE = re.compile(r'.*/\.((Xauthority)|(serverauth\.[0-9]*))$') +XAPI_DEBUG_DIR = '/var/xapi/debug' +LOG_CONF = '/etc/xensource/log.conf' +INSTALLED_REPOS_DIR = '/etc/xensource/installed-repos' +PATCH_APPLIED_DIR = '/var/patch/applied' +XENSERVER_LOGS = \ + [ VAR_LOG_DIR + x for x in + ['xensource.log', 'xenstored-access.log', 'SMlog', 'xen/xenstored-trace.log', + 'xen/xen-hotplug.log', 'xen/domain-builder-ng.log'] + + [ f % n for n in range(1, 20) \ + for f in ['xensource.log.%d', 'xensource.log.%d.gz','SMlog.%d', 'SMlog.%d.gz', + 'xenstored-access.log.%d', 'xenstored-access.log.%d.gz', \ + 'xen/xenstored-access.log.%d', 'xen/xenstored-access.log.%d.gz' ]]] \ + + glob.glob('/tmp/qemu.[0-9]*') +OEM_XENSERVER_LOGS_RE = re.compile(r'^.*xensource\.log$') +XHA_LOG = '/var/log/xha.log' +XHAD_CONF = '/etc/xensource/xhad.conf' +YUM_LOG = '/var/log/yum.log' +YUM_REPOS_DIR = '/etc/yum.repos.d' +PAM_DIR = '/etc/pam.d' + + +# +# External programs +# + +ARP = '/sbin/arp' +BIOSDEVNAME = '/sbin/biosdevname' +BRCTL = '/usr/sbin/brctl' +CAT = '/bin/cat' +CHKCONFIG = '/sbin/chkconfig' +CSL = '/opt/Citrix/StorageLink/bin/csl' +DF = '/bin/df' +DMESG = '/bin/dmesg' +DMIDECODE = '/usr/sbin/dmidecode' +DMSETUP = '/sbin/dmsetup' +ETHTOOL = '/sbin/ethtool' +FDISK = '/sbin/fdisk' +FIND = '/usr/bin/find' +HA_QUERY_LIVESET = '/opt/xensource/debug/debug_ha_query_liveset' +HDPARM = '/sbin/hdparm' +IFCONFIG = '/sbin/ifconfig' +IPTABLES = '/sbin/iptables' +ISCSIADM = '/sbin/iscsiadm' +LIST_DOMAINS = '/opt/xensource/bin/list_domains' +LOSETUP = '/sbin/losetup' +LS = '/bin/ls' +LSPCI = '/sbin/lspci' +LVS = '/usr/sbin/lvs' +MD5SUM = '/usr/bin/md5sum' +MULTIPATHD = '/sbin/multipathd' +NETSTAT = '/bin/netstat' +OMREPORT = '/opt/dell/srvadmin/oma/bin/omreport' +OVS_DPCTL = '/root/vswitch/bin/ovs-dpctl' +OVS_OFCTL = '/root/vswitch/bin/ovs-ofctl' +PS = '/bin/ps' +PVS = '/usr/sbin/pvs' +ROUTE = '/sbin/route' +RPM = '/bin/rpm' +SG_MAP = '/usr/bin/sg_map' +SQLITE = '/usr/bin/sqlite3' +BIN_STATIC_VDIS = '/opt/xensource/bin/static-vdis' +SYSCTL = '/sbin/sysctl' +TC = '/sbin/tc' +UPTIME = '/usr/bin/uptime' +VGS = '/usr/sbin/vgs' +VGSCAN = '/sbin/vgscan' +XAPI_DB_PROCESS = '/opt/xensource/bin/xapi-db-process' +XE = '/opt/xensource/bin/xe' +XS = '/opt/xensource/debug/xs' +XENSTORE_LS = '/usr/bin/xenstore-ls' +ZCAT = '/bin/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 + +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_BLOBS = 'blobs' +CAP_BOOT_LOADER = 'boot-loader' +CAP_CVSM = 'CVSM' +CAP_DISK_INFO = 'disk-info' +CAP_FIRSTBOOT = 'firstboot' +CAP_HARDWARE_INFO = 'hardware-info' +CAP_HDPARM_T = 'hdparm-t' +CAP_HIGH_AVAILABILITY = 'high-availability' +CAP_HOST_CRASHDUMP_DUMPS = 'host-crashdump-dumps' +CAP_HOST_CRASHDUMP_LOGS = 'host-crashdump-logs' +CAP_KERNEL_INFO = 'kernel-info' +CAP_LOSETUP_A = 'loopback-devices' +CAP_MULTIPATH = 'multipath' +CAP_NETWORK_CONFIG = 'network-config' +CAP_NETWORK_STATUS = 'network-status' +CAP_OEM = 'oem' +CAP_PAM = 'pam' +CAP_PROCESS_LIST = 'process-list' +CAP_PERSISTENT_STATS = 'persistent-stats' +CAP_SYSTEM_LOGS = 'system-logs' +CAP_SYSTEM_SERVICES = 'system-services' +CAP_TAPDISK_LOGS = 'tapdisk-logs' +CAP_VNCTERM = 'vncterm' +CAP_VSWITCH_CONFIG = 'vswitch-config' +CAP_VSWITCH_STATUS = 'vswitch-status' +CAP_WLB = 'wlb' +CAP_X11_LOGS = 'X11' +CAP_X11_AUTH = 'X11-auth' +CAP_XAPI_DEBUG = 'xapi-debug' +CAP_XAPI_SUBPROCESS = 'xapi-subprocess' +CAP_XENSERVER_CONFIG = 'xenserver-config' +CAP_XENSERVER_DOMAINS = 'xenserver-domains' +CAP_XENSERVER_DATABASES = 'xenserver-databases' +CAP_XENSERVER_INSTALL = 'xenserver-install' +CAP_XENSERVER_LOGS = 'xenserver-logs' +CAP_XEN_INFO = 'xen-info' +CAP_XHA_LIVESET = 'xha-liveset' +CAP_YUM = 'yum' + +KB = 1024 +MB = 1024 * 1024 + +caps = {} +cap_sizes = {} +unlimited_data = False +dbg = False + +def cap(key, pii=PII_MAYBE, min_size=-1, max_size=-1, min_time=-1, + max_time=-1, mime=MIME_TEXT, checked=True): + caps[key] = (key, pii, min_size, max_size, min_time, max_time, mime, + checked) + cap_sizes[key] = 0 + + +cap(CAP_BLOBS, PII_NO, max_size=5*MB) +cap(CAP_BOOT_LOADER, PII_NO, max_size=3*KB, + max_time=5) +cap(CAP_CVSM, PII_NO, max_size=3*MB, + max_time=60) +cap(CAP_DISK_INFO, PII_MAYBE, max_size=25*KB, + max_time=20) +cap(CAP_FIRSTBOOT, PII_YES, min_size=60*KB, max_size=80*KB) +cap(CAP_HARDWARE_INFO, PII_MAYBE, max_size=30*KB, + max_time=20) +cap(CAP_HDPARM_T, PII_NO, min_size=0, max_size=5*KB, + min_time=20, max_time=90, checked=False) +cap(CAP_HIGH_AVAILABILITY, PII_MAYBE, max_size=5*MB) +cap(CAP_HOST_CRASHDUMP_DUMPS,PII_YES, checked = False) +cap(CAP_HOST_CRASHDUMP_LOGS, PII_NO) +cap(CAP_KERNEL_INFO, PII_MAYBE, max_size=80*KB, + max_time=5) +cap(CAP_LOSETUP_A, PII_MAYBE, max_size=KB, max_time=5) +cap(CAP_MULTIPATH, PII_MAYBE, max_size=10*KB, + max_time=10) +cap(CAP_NETWORK_CONFIG, PII_IF_CUSTOMIZED, + min_size=0, max_size=20*KB) +cap(CAP_NETWORK_STATUS, PII_YES, max_size=19*KB, + max_time=30) +cap(CAP_PAM, PII_NO, max_size=10*KB) +cap(CAP_PERSISTENT_STATS, PII_MAYBE, max_size=50*MB, + max_time=60) +cap(CAP_PROCESS_LIST, PII_YES, max_size=10*KB, + max_time=10) +cap(CAP_SYSTEM_LOGS, PII_MAYBE, max_size=50*MB, + max_time=5) +cap(CAP_SYSTEM_SERVICES, PII_NO, max_size=5*KB, + max_time=20) +cap(CAP_TAPDISK_LOGS, PII_NO, max_size=64*KB) +cap(CAP_VNCTERM, PII_MAYBE, checked = False) +cap(CAP_VSWITCH_CONFIG, PII_YES, + min_size=0, max_size=20*MB) +cap(CAP_VSWITCH_STATUS, PII_YES, max_size=19*KB, + max_time=30) +cap(CAP_WLB, PII_NO, max_size=3*MB, + max_time=20) +cap(CAP_X11_LOGS, PII_NO, max_size=100*KB) +cap(CAP_X11_AUTH, PII_NO, max_size=100*KB) +cap(CAP_XAPI_DEBUG, PII_MAYBE, max_size=10*MB) +cap(CAP_XAPI_SUBPROCESS, PII_NO, max_size=5*KB, + max_time=10) +cap(CAP_XENSERVER_CONFIG, PII_MAYBE, max_size=50*KB, + max_time=5) +cap(CAP_XENSERVER_DOMAINS, PII_NO, max_size=1*KB, + max_time=5) +cap(CAP_XENSERVER_DATABASES, PII_YES, min_size=500*KB,max_size=2*MB, + max_time=20) +cap(CAP_XENSERVER_INSTALL, PII_MAYBE, min_size=10*KB, max_size=300*KB) +cap(CAP_XENSERVER_LOGS, PII_MAYBE, min_size=0, max_size=50*MB) +cap(CAP_XEN_INFO, PII_MAYBE, max_size=20*KB, + max_time=10) +cap(CAP_XHA_LIVESET, PII_MAYBE, max_size=10*KB, + max_time=10) +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): + if cap in entries: + a = [aa for aa in args] + a[0] = os.path.basename(a[0]) + if not label: + label = ' '.join(a) + data[label] = {'cap': cap, 'cmd_args': args, 'filter': filter} + +def file_output(cap, path_list): + if cap in entries: + for p in path_list: + if os.path.exists(p): + if unlimited_data or caps[cap][MAX_SIZE] == -1 or \ + cap_sizes[cap] < caps[cap][MAX_SIZE]: + data[p] = {'cap': cap, 'filename': p} + try: + s = os.stat(p) + cap_sizes[cap] += s.st_size + except: + pass + else: + output("Omitting %s, size constraint of %s exceeded" % (p, cap)) + +def tree_output(cap, path, pattern = None, negate = False): + if cap in entries: + if os.path.exists(path): + for f in os.listdir(path): + fn = os.path.join(path, f) + if os.path.isfile(fn) and matches(fn, pattern, negate): + file_output(cap, [fn]) + elif os.path.isdir(fn): + tree_output(cap, fn, pattern, negate) + +def func_output(cap, label, func): + if cap in entries: + t = str(func).split() + data[label] = {'cap': cap, 'func': func} + +def collect_data(): + process_lists = {} + + for (k, v) in data.items(): + cap = v['cap'] + if v.has_key('cmd_args'): + v['output'] = StringIOmtime() + if not process_lists.has_key(cap): + process_lists[cap] = [] + process_lists[cap].append(ProcOutput(v['cmd_args'], caps[cap][MAX_TIME], v['output'], v['filter'])) + elif v.has_key('filename') and v['filename'].startswith('/proc/'): + # proc files must be read into memory + try: + f = open(v['filename'], 'r') + s = f.read() + f.close() + if unlimited_data or caps[cap][MAX_SIZE] == -1 or \ + cap_sizes[cap] < caps[cap][MAX_SIZE]: + v['output'] = StringIOmtime(s) + cap_sizes[cap] += len(s) + else: + output("Omitting %s, size constraint of %s exceeded" % (v['filename'], cap)) + except: + pass + elif v.has_key('func'): + try: + s = v['func'](cap) + except Exception, e: + s = str(e) + if unlimited_data or caps[cap][MAX_SIZE] == -1 or \ + cap_sizes[cap] < caps[cap][MAX_SIZE]: + v['output'] = StringIOmtime(s) + cap_sizes[cap] += len(s) + else: + output("Omitting %s, size constraint of %s exceeded" % (k, cap)) + + run_procs(process_lists.values()) + + +def main(argv = None): + global ANSWER_YES_TO_ALL, SILENT_MODE + global entries, data, dbg + + # we need access to privileged files, exit if we are not running as root + if os.getuid() != 0: + print >>sys.stderr, "Error: xen-bugtool must be run as root" + return 1 + + output_type = 'tar.bz2' + output_fd = -1 + + if argv is None: + argv = sys.argv + + try: + (options, params) = getopt.gnu_getopt( + argv, 'sy', ['capabilities', 'silent', 'yestoall', 'entries=', + 'output=', 'outfd=', 'all', 'unlimited', 'debug']) + except getopt.GetoptError, opterr: + print >>sys.stderr, opterr + return 2 + + inventory = readKeyValueFile(XENSOURCE_INVENTORY) + if inventory.has_key('OEM_BUILD_NUMBER'): + cap(CAP_OEM, PII_MAYBE, max_size=5*MB, + max_time=90) + + if os.getenv('XEN_RT'): + entries = [CAP_BLOBS, CAP_BOOT_LOADER, CAP_CVSM, CAP_DISK_INFO, CAP_FIRSTBOOT, CAP_HARDWARE_INFO, + CAP_HOST_CRASHDUMP_DUMPS, CAP_HOST_CRASHDUMP_LOGS, CAP_KERNEL_INFO, CAP_LOSETUP_A, + CAP_NETWORK_CONFIG, CAP_NETWORK_STATUS, CAP_PROCESS_LIST, CAP_HIGH_AVAILABILITY, + CAP_PAM, CAP_PERSISTENT_STATS, CAP_MULTIPATH, + CAP_SYSTEM_LOGS, CAP_SYSTEM_SERVICES, CAP_TAPDISK_LOGS, + CAP_VNCTERM, CAP_VSWITCH_CONFIG, CAP_VSWITCH_STATUS, CAP_WLB, + CAP_X11_LOGS, CAP_X11_AUTH, CAP_XAPI_DEBUG, CAP_XAPI_SUBPROCESS, + CAP_XENSERVER_CONFIG, CAP_XENSERVER_DOMAINS, CAP_XENSERVER_DATABASES, + CAP_XENSERVER_INSTALL, CAP_XENSERVER_LOGS, CAP_XEN_INFO, CAP_XHA_LIVESET, CAP_YUM] + else: + 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', 'zip']: + output_type = v + else: + print >>sys.stderr, "Invalid output format '%s'" % v + 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 >>sys.stderr, "Invalid output file descriptor", output_fd + return 2 + + elif k == '--all': + entries = caps.keys() + elif k == '--unlimited': + unlimited_data = True + elif k == '--debug': + dbg = True + ProcOutput.debug = True + + if len(params) != 1: + print >>sys.stderr, "Invalid additional arguments", str(params) + return 2 + + if output_fd != -1 and output_type != 'tar': + print >>sys.stderr, "Option '--outfd' only valid with '--output=tar'" + return 2 + + if ANSWER_YES_TO_ALL: + output("Warning: '--yestoall' argument provided, will not prompt for individual files.") + + output(''' +This application will collate the Xen dmesg output, details of the +hardware configuration of your machine, information about the build of +Xen 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 + tree_output(CAP_BLOBS, XAPI_BLOBS) + + file_output(CAP_BOOT_LOADER, [GRUB_CONFIG, EXTLINUX_CONFIG]) + cmd_output(CAP_BOOT_LOADER, [LS, '-lR', '/boot']) + cmd_output(CAP_BOOT_LOADER, [MD5SUM, BOOT_KERNEL, BOOT_INITRD], label='vmlinuz-initrd.md5sum') + + func_output(CAP_CVSM, 'csl_logs', csl_logs) + + 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']) + for d in disk_list(): + cmd_output(CAP_DISK_INFO, [HDPARM, '-I', '/dev/%s' % d]) + if len(pidof('iscsid')) != 0: + cmd_output(CAP_DISK_INFO, [ISCSIADM, '-m', 'node']) + cmd_output(CAP_DISK_INFO, [VGSCAN]) + cmd_output(CAP_DISK_INFO, [PVS]) + cmd_output(CAP_DISK_INFO, [VGS]) + cmd_output(CAP_DISK_INFO, [LVS]) + file_output(CAP_DISK_INFO, [LVM_CACHE]) + 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) + tree_output(CAP_DISK_INFO, PROC_DRIVER_CCISS_DIR) + + tree_output(CAP_FIRSTBOOT, FIRSTBOOT_DIR) + + 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, [BOOT_TIME_CPUS, BOOT_TIME_MEMORY]) + file_output(CAP_HARDWARE_INFO, [SYSCONFIG_HWCONF]) + # FIXME IDE? + + for d in disk_list(): + cmd_output(CAP_HDPARM_T, [HDPARM, '-tT', '/dev/%s' % d]) + + file_output(CAP_HIGH_AVAILABILITY, [XHAD_CONF, XHA_LOG]) + + tree_output(CAP_HOST_CRASHDUMP_DUMPS, HOST_CRASHDUMPS_DIR, + HOST_CRASHDUMP_LOGS_RE, True) + tree_output(CAP_HOST_CRASHDUMP_LOGS, HOST_CRASHDUMPS_DIR, + HOST_CRASHDUMP_LOGS_RE, False) + + 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) + + cmd_output(CAP_LOSETUP_A, [LOSETUP, '-a']) + + file_output(CAP_MULTIPATH, [MULTIPATH_CONF]) + cmd_output(CAP_MULTIPATH, [DMSETUP, 'status']) + func_output(CAP_MULTIPATH, 'multipathd_topology', multipathd_topology) + + tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, IFCFG_RE) + tree_output(CAP_NETWORK_CONFIG, SYSCONFIG_NETWORK_SCRIPTS, ROUTE_RE) + file_output(CAP_NETWORK_CONFIG, [SYSCONFIG_NETWORK, RESOLV_CONF, NSSWITCH_CONF]) + file_output(CAP_NETWORK_CONFIG, [NTP_CONF, IPTABLES_CONFIG, HOSTS_ALLOW, HOSTS_DENY]) + + cmd_output(CAP_NETWORK_STATUS, [IFCONFIG, '-a']) + cmd_output(CAP_NETWORK_STATUS, [ROUTE, '-n']) + cmd_output(CAP_NETWORK_STATUS, [ARP, '-n']) + cmd_output(CAP_NETWORK_STATUS, [NETSTAT, '-an']) + tree_output(CAP_NETWORK_STATUS, DHCP_LEASE_DIR) + cmd_output(CAP_NETWORK_STATUS, [IPTABLES, '-nL']) + cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'show']) + cmd_output(CAP_NETWORK_STATUS, [BIOSDEVNAME, '-d']) + for p in os.listdir('/sys/class/net/'): + if os.path.isdir('/sys/class/net/%s/bridge' % p): + cmd_output(CAP_NETWORK_STATUS, [BRCTL, 'showmacs', p]) + else: + try: + f = open('/sys/class/net/%s/type' % p, 'r') + t = f.readline() + f.close() + if int(t) == 1: + # ARPHRD_ETHER + cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, p]) + cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-S', p]) + cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-k', p]) + cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-i', p]) + cmd_output(CAP_NETWORK_STATUS, [ETHTOOL, '-c', p]) + except: + pass + tree_output(CAP_NETWORK_STATUS, PROC_NET_BONDING_DIR) + tree_output(CAP_NETWORK_STATUS, PROC_NET_VLAN_DIR) + cmd_output(CAP_NETWORK_STATUS, [TC, '-s', 'qdisc']) + file_output(CAP_NETWORK_STATUS, [PROC_NET_SOFTNET_STAT]) + + tree_output(CAP_OEM, DELL_OMSA_LOGS) + file_output(CAP_OEM, [HP_CMA_LOG, HP_HPASMD_LOG]) + if os.path.exists(OMREPORT): + cmd_output(CAP_OEM, [OMREPORT, 'system', 'alertlog']) + cmd_output(CAP_OEM, [OMREPORT, 'system', 'cmdlog']) + cmd_output(CAP_OEM, [OMREPORT, 'system', 'esmlog']) + cmd_output(CAP_OEM, [OMREPORT, 'system', 'postlog']) + cmd_output(CAP_OEM, [OMREPORT, 'chassis', 'fans']) + cmd_output(CAP_OEM, [OMREPORT, 'chassis', 'memory']) + cmd_output(CAP_OEM, [OMREPORT, 'chassis', 'temps']) + cmd_output(CAP_OEM, [OMREPORT, 'storage', 'controller']) + for i in range(0, 4): + cmd_output(CAP_OEM, [OMREPORT, 'storage', 'adisk', 'controller=%d' % i]) + cmd_output(CAP_OEM, [OMREPORT, 'storage', 'vdisk', 'controller=%d' % i]) + cmd_output(CAP_OEM, [FIND, '/.state', '-size', '+20k', '-exec', 'ls', '-l', '{}',';'], + label = "state+20k") + + tree_output(CAP_PAM, PAM_DIR) + + func_output(CAP_PERSISTENT_STATS, 'xapi_rrd-host', dump_xapi_rrds) + + cmd_output(CAP_PROCESS_LIST, [PS, 'wwwaxf', '-eo', 'pid,tty,stat,time,nice,psr,pcpu,pmem,wchan:25,args'], label='process-tree') + + file_output(CAP_SYSTEM_LOGS, + [ VAR_LOG_DIR + x for x in + [ 'syslog', 'messages', 'monitor_memory.log', 'secure', 'debug', 'dmesg', 'boot.msg' ] + + [ f % n for n in range(1, 20) \ + for f in ['messages.%d', 'messages.%d.gz', 'monitor_memory.log.%d', + 'monitor_memory.log.%d.gz', 'secure.%d', 'secure.%d.gz']]]) + if not os.path.exists('/var/log/dmesg') and not os.path.exists('/var/log/boot.msg'): + cmd_output(CAP_SYSTEM_LOGS, [DMESG]) + + cmd_output(CAP_SYSTEM_SERVICES, [CHKCONFIG, '--list']) + + if CAP_TAPDISK_LOGS in entries: + generate_tapdisk_logs() + + tree_output(CAP_VNCTERM, VNCTERM_CORE_DIR) + + file_output(CAP_VSWITCH_CONFIG, [OVS_VSWITCH_CONF]) + + cmd_output(CAP_VSWITCH_STATUS, [OVS_DPCTL, 'show']) + tree_output(CAP_VSWITCH_STATUS, VSWITCH_CORE_DIR) + for d in dp_list(): + cmd_output(CAP_VSWITCH_STATUS, [OVS_OFCTL, 'show', d]) + cmd_output(CAP_VSWITCH_STATUS, [OVS_OFCTL, 'status', d]) + cmd_output(CAP_VSWITCH_STATUS, [OVS_OFCTL, 'dump-flows', d]) + cmd_output(CAP_VSWITCH_STATUS, [OVS_DPCTL, 'dump-flows', d]) + + cmd_output(CAP_WLB, [XE, 'pool-retrieve-wlb-diagnostics']) + + tree_output(CAP_X11_LOGS, X11_LOGS_DIR, X11_LOGS_RE) + tree_output(CAP_X11_AUTH, X11_AUTH_DIR, X11_AUTH_RE) + + tree_output(CAP_XAPI_DEBUG, XAPI_DEBUG_DIR) + + func_output(CAP_XAPI_SUBPROCESS, 'xapi_subprocesses', dump_xapi_subprocess_info) + + file_output(CAP_XENSERVER_CONFIG, [INITIAL_INVENTORY]) + file_output(CAP_XENSERVER_CONFIG, [POOL_CONF, PTOKEN, XAPI_CONF, XAPI_SSL_CONF, STATIC_VDIS, + XENSOURCE_INVENTORY, VENDORKERNEL_INVENTORY]) + cmd_output(CAP_XENSERVER_CONFIG, [LS, '-lR', '/opt/xensource']) + cmd_output(CAP_XENSERVER_CONFIG, [BIN_STATIC_VDIS, 'list']) + tree_output(CAP_XENSERVER_CONFIG, OEM_CONFIG_DIR, OEM_CONFIG_FILES_RE) + + func_output(CAP_XENSERVER_DATABASES, 'xapi-db.xml', dump_filtered_xapi_db) + cmd_output(CAP_XENSERVER_DATABASES, [XENSTORE_LS]) + file_output(CAP_XENSERVER_DATABASES, [DB_CONF, DB_CONF_RIO, DB_DEFAULT_FIELDS, DB_SCHEMA_SQL]) + tree_output(CAP_XENSERVER_DATABASES, OEM_CONFIG_DIR, OEM_DB_FILES_RE) + file_output(CAP_XENSERVER_DATABASES, [XENSTORED_DB, XENSTORED_DB + '.bak']) + cmd_output(CAP_XENSERVER_DATABASES, [XE, 'pool-dump-database', 'file-name='], + label="xapi-db-dumped.xml", filter=filter_db_pii) + cmd_output(CAP_XENSERVER_DATABASES, [XS, 'debug', 'watches']) + cmd_output(CAP_XENSERVER_DATABASES, [XS, 'debug', 'quotas']) + + cmd_output(CAP_XENSERVER_DOMAINS, [LIST_DOMAINS]) + + tree_output(CAP_XENSERVER_INSTALL, VAR_LOG_DIR + 'installer') + file_output(CAP_XENSERVER_INSTALL, + [ VAR_LOG_DIR + x for x in + [ 'firstboot-SR-commands-log', + 'upgrade-commands-log', 'generate-iscsi-iqn-log']] + + [ '/root/' + x for x in + [ 'blockdevs-log', 'cmdline-log', 'devcontents-log', + 'dmesg-log', 'install-log', 'lspci-log', 'modules-log', + 'pci-log', 'processes-log', 'tty-log', 'uname-log', + 'vgscan-log']]) + tree_output(CAP_XENSERVER_INSTALL, INSTALLED_REPOS_DIR) + tree_output(CAP_XENSERVER_INSTALL, PATCH_APPLIED_DIR) + + file_output(CAP_XENSERVER_LOGS, [LOG_CONF]) + file_output(CAP_XENSERVER_LOGS, XENSERVER_LOGS) + tree_output(CAP_XENSERVER_LOGS, OEM_CONFIG_DIR, OEM_XENSERVER_LOGS_RE) + + try: + def xen_dmesg(xc): + data = xc.readconsolering() + xc.send_debug_keys('q') + time.sleep(1) + return data + + xc = xen.lowlevel.xc.xc() + + func_output(CAP_XEN_INFO, 'xen-dmesg', lambda x: xen_dmesg(xc)) + func_output(CAP_XEN_INFO, 'physinfo', lambda x: prettyDict(xc.physinfo())) + func_output(CAP_XEN_INFO, 'xeninfo', lambda x: prettyDict(xc.xeninfo())) + except: + pass + file_output(CAP_XEN_INFO, [PROC_XEN_BALLOON]) + + cmd_output(CAP_XHA_LIVESET, [HA_QUERY_LIVESET]) + + file_output(CAP_YUM, [YUM_LOG]) + tree_output(CAP_YUM, YUM_REPOS_DIR) + cmd_output(CAP_YUM, [RPM, '-qa']) + + # permit the user to filter out data + for k in sorted(data.keys()): + if not ANSWER_YES_TO_ALL and not yes("Include '%s'? [Y/n]: " % k): + del data[k] + + # collect selected data now + output_ts('Running commands to collect data') + collect_data() + + subdir = os.getenv('XENRT_BUGTOOL_BASENAME') + if subdir: + subdir = os.path.basename(subdir) + if subdir == '..' or subdir == '.': + subdir = None + if not subdir: + subdir = "bug-report-%s" % time.strftime("%Y%m%d%H%M%S") + + # include inventory + data['inventory.xml'] = {'cap': None, 'output': StringIOmtime(make_inventory(data, subdir))} + + # create archive + if output_fd == -1 and not os.path.exists(BUG_DIR): + try: + os.makedirs(BUG_DIR) + except: + pass + + if output_fd == -1: + output_ts('Creating output file') + + if output_type.startswith('tar'): + make_tar(subdir, output_type, output_fd) + else: + make_zip(subdir) + + clean_tapdisk_logs() + + if dbg: + print >>sys.stderr, "Category sizes (max, actual):\n" + for c in caps.keys(): + print >>sys.stderr, " %s (%d, %d)" % (c, caps[c][MAX_SIZE], + cap_sizes[c]) + return 0 + +def generate_tapdisk_logs(): + for pid in pidof('tapdisk'): + try: + os.kill(pid, SIGUSR1) + output_ts("Including logs for tapdisk process %d" % pid) + except : + pass + # give processes a second to write their logs + time.sleep(1) + file_output(CAP_TAPDISK_LOGS, ['/tmp/tapdisk.log.%d' % pid for pid in pidof('tapdisk')]) + +def clean_tapdisk_logs(): + for filename in [f for f in os.listdir('/tmp') if f.startswith('tapdisk.log.')]: + try: + os.remove(os.path.join('tmp', filename)) + except : + pass + +def dump_xapi_subprocess_info(cap): + """Check which fds are open by xapi and its subprocesses to diagnose faults like CA-10543. + Returns a string containing a pretty-printed pstree-like structure. """ + pids = filter(lambda x: x.isdigit(), os.listdir("/proc")) + def readlines(filename): + lines = '' + try: + f = open(filename, "r") + lines = f.readlines() + f.close() + except: + pass + return lines + def cmdline(pid): + all = readlines("/proc/" + pid + "/cmdline") + if all == []: + return "" + else: + return all[0].replace('\x00', ' ') + def parent(pid): + for i in readlines("/proc/" + pid + "/status"): + if i.startswith("PPid:"): + return i.split()[-1] + return None + def pstree(pid): + result = { "cmdline": cmdline(pid) } + child_pids = filter(lambda x:parent(x) == pid, pids) + children = { } + for child in child_pids: + children[child] = pstree(child) + result['children'] = children + fds = { } + for fd in os.listdir("/proc/" + pid + "/fd"): + try: + fds[fd] = os.readlink("/proc/" + pid + "/fd/" + fd) + except: + pass + result['fds'] = fds + return result + xapis = filter(lambda x: cmdline(x).startswith("/opt/xensource/bin/xapi"), pids) + xapis = filter(lambda x: parent(x) == "1", xapis) + result = {} + for xapi in xapis: + result[xapi] = pstree(xapi) + pp = pprint.PrettyPrinter(indent=4) + return pp.pformat(result) + +def dump_xapi_rrds(cap): + socket.setdefaulttimeout(5) + session = XenAPI.xapi_local() + session.xenapi.login_with_password('', '') + this_host = session.xenapi.session.get_this_host(session._session) + # better way to find pool master? + pool = session.xenapi.pool.get_all_records().values()[0] + i_am_master = (this_host == pool['master']) + + for vm in session.xenapi.VM.get_all_records().values(): + if vm['is_a_template']: + continue + if vm['resident_on'] == this_host or (i_am_master and vm['power_state'] in ['Suspended', 'Halted']): + rrd = urllib.urlopen('http://localhost/vm_rrd?session_id=%s&uuid=%s' % (session._session, vm['uuid'])) + try: + (i, o, x) = select([rrd], [], [], 5.0) + if len(i) == 1: + data['xapi_rrd-%s' % vm['uuid']] = {'cap': cap, + 'output': StringIOmtime(rrd.read())} + finally: + rrd.close() + + output = '' + rrd = urllib.urlopen('http://localhost/host_rrd?session_id=%s' % session._session) + try: + for line in rrd: + output += line + finally: + rrd.close() + + session.xenapi.session.logout() + return output + +def filter_db_pii(str): + str = re.sub(r'(password_transformed" ")[^ ]+(")', r'\1REMOVED\2', str) + str = re.sub(r'(wlb_password=")[^"]+(")', r'\1REMOVED\2', str) + return str + +def dump_filtered_xapi_db(cap): + db_file = None + format = None + + # determine db format + c = open(DB_CONF, 'r') + try: + for line in c: + l = line.rstrip('\n') + if l.startswith('['): + db_file = l[1:-1] + if l.startswith('format:'): + format = l[7:] + break + finally: + c.close() + + pipe = None + ih = None + output = '' + + if format == 'sqlite': + pipe = Popen([XAPI_DB_PROCESS, '-xmltostdout'], bufsize=1, stdin=dev_null, + stdout=PIPE, stderr=dev_null) + ih = pipe.stdout + elif db_file: + ih = open(db_file, 'r') + + if not ih: + return '' + + remain = '' + rec = ih.read(2048) + while rec != '': + remain += rec + p = remain.find('>') + while p != -1: + str = remain[:p+1] + remain = remain[p+1:] + output += filter_db_pii(str) + p = remain.find('>') + rec = ih.read(2048) + output += remain + + if pipe: + pipe.wait() + else: + ih.close() + return output + +def dump_scsi_hosts(cap): + output = '' + l = os.listdir('/sys/class/scsi_host') + l.sort() + + for h in l: + 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 csl_logs(cap): + socket.setdefaulttimeout(5) + session = XenAPI.xapi_local() + session.xenapi.login_with_password('', '') + this_host = session.xenapi.session.get_this_host(session._session) + # better way to find pool master? + pool = session.xenapi.pool.get_all_records().values()[0] + i_am_master = (this_host == pool['master']) + + output = StringIO.StringIO() + procs = [] + + def rotate_string(x, n): + transtbl = "" + for a in range(0, 256): + transtbl = transtbl + chr(a) + transtbl = transtbl[n:] + transtbl[0:n] + return x.translate(transtbl) + + def _untransform_string(str, remove_trailing_nulls=False): + """De-obfuscate string. To cope with an obfuscation bug in Rio, the argument + remove_trailing_nulls should be set to True""" + tmp = base64.decodestring(str) + if remove_trailing_nulls: + tmp = tmp.rstrip('\x00') + return rotate_string(tmp, -13) + + for pbd in session.xenapi.PBD.get_all_records().values(): + if pbd.has_key('device_config') and pbd['device_config'].has_key('target'): + sr = session.xenapi.SR.get_record(pbd['SR']) + if sr.has_key('type') and sr['type'] == 'cslg': + if sr['shared'] and pbd['host'] != this_host and not i_am_master: + continue + + dev_cfg = pbd['device_config'] + server = "server=%s" % socket.gethostbyname(dev_cfg['target']) + if dev_cfg.has_key('port'): + server += ':' + dev_cfg['port'] + if dev_cfg.has_key('username'): + server += ',' + dev_cfg['username'] + if dev_cfg.has_key('password_transformed'): + server += ',' + _untransform_string(dev_cfg['password_transformed']) + procs.append(ProcOutput([CSL, server, 'srv-log-get'], caps[cap][MAX_TIME], output)) + + session.xenapi.session.logout() + + 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 make_tar(subdir, suffix, output_fd): + global SILENT_MODE, data + + mode = 'w' + if suffix == 'tar.bz2': + mode = 'w:bz2' + filename = "%s/%s.%s" % (BUG_DIR, subdir, suffix) + + if output_fd == -1: + tf = tarfile.open(filename, mode) + 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 v.has_key('output'): + ti.mtime = v['output'].mtime + ti.size = len(v['output'].getvalue()) + v['output'].seek(0) + tf.addfile(ti, v['output']) + elif v.has_key('filename'): + s = os.stat(v['filename']) + ti.mtime = s.st_mtime + ti.size = s.st_size + tf.addfile(ti, file(v['filename'])) + except: + pass + finally: + tf.close() + + if output_fd == -1: + output ('Writing tarball %s successful.' % filename) + if SILENT_MODE: + print filename + + +def make_zip(subdir): + global SILENT_MODE, data + + filename = "%s/%s.zip" % (BUG_DIR, subdir) + zf = zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED) + + try: + for (k, v) in data.items(): + try: + dest = os.path.join(subdir, construct_filename(k, v)) + + if v.has_key('output'): + 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', commands.getoutput(UPTIME)) + document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(s) + + map(lambda (k, v): inventory_entry(document, subdir, k, v), + inventory.items()) + return document.toprettyxml() + +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('md5sum', md5sum(v)) + document.getElementsByTagName(INVENTORY_XML_ROOT)[0].appendChild(el) + except: + pass + + +def md5sum(d): + m = md5.new() + if d.has_key('filename'): + f = open(d['filename']) + data = f.read(1024) + while len(data) > 0: + m.update(data) + data = f.read(1024) + f.close() + elif d.has_key('output'): + m.update(d['output'].getvalue()) + return m.hexdigest() + + +def construct_filename(k, v): + if v.has_key('filename'): + 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(): + update_cap_size(CAP_HOST_CRASHDUMP_LOGS, + size_of_dir(HOST_CRASHDUMPS_DIR, HOST_CRASHDUMP_LOGS_RE)) + update_cap_size(CAP_HOST_CRASHDUMP_DUMPS, + size_of_dir(HOST_CRASHDUMPS_DIR, HOST_CRASHDUMP_LOGS_RE, + True)) + update_cap_size(CAP_XAPI_DEBUG, size_of_dir(XAPI_DEBUG_DIR)) + update_cap_size(CAP_XENSERVER_LOGS, size_of_all(XENSERVER_LOGS)) + + +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 + l = list(caps[cap]) + l[k] = v + caps[cap] = tuple(l) + + +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), caps.keys()) + 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 = raw_input(prompt) + + return len(yn) == 0 or yn.lower()[0] == 'y' + + +partition_re = re.compile(r'(.*[0-9]+$)|(^xvd)') + +def dp_list(): + command = [OVS_DPCTL, "dump-dps"] + proc = Popen(command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null) + (dps, err) = proc.communicate() + return dps.splitlines() + + +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: + debug = False + def __init__(self, command, max_time, inst=None, filter=None): + 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 + + def __del__(self): + self.terminate() + + def run(self): + self.timed_out = False + try: + if ProcOutput.debug: + output_ts("Starting '%s'" % ' '.join(self.command)) + self.proc = Popen(self.command, bufsize=1, stdin=dev_null, stdout=PIPE, stderr=dev_null) + 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" % ' '.join(self.command)) + self.running = False + self.failed = True + + def terminate(self): + if self.running: + try: + os.kill(self.proc.pid, SIGTERM) + except: + pass + self.proc = None + self.running = False + self.status = SIGTERM + + def read_line(self): + assert self.running + line = self.proc.stdout.readline() + if line == '': + # process exited + self.status = self.proc.wait() + self.proc = None + self.running = False + else: + if self.filter: + line = self.filter(line) + 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 == 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" % ' '.join(p.command)) + if p.inst: + p.inst.write("\n** timeout **\n") + 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 readKeyValueFile(filename, allowed_keys = None, strip_quotes = True, assert_quotes = True): + """ Reads a KEY=Value style file (e.g. xensource-inventory). Returns a + dictionary of key/values in the file. Not designed for use with large files + as the file is read entirely into memory.""" + + f = open(filename, "r") + lines = [x.strip("\n") for x in f.readlines()] + f.close() + + # remove lines contain + if allowed_keys: + lines = filter(lambda x: True in [x.startswith(y) for y in allowed_keys], + lines) + + defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ] + + if strip_quotes: + def quotestrip(x): + if assert_quotes: + assert x.startswith("'") and x.endswith("'") + return x.strip("'") + defs = [ (a, quotestrip(b)) for (a,b) in defs ] + + return dict(defs) + + +class StringIOmtime(StringIO.StringIO): + def __init__(self, buf = ''): + StringIO.StringIO.__init__(self, buf) + self.mtime = time.time() + + def write(self, s): + StringIO.StringIO.write(self, s) + self.mtime = time.time() + + +if __name__ == "__main__": + try: + sys.exit(main()) + except KeyboardInterrupt: + print "\nInterrupted." + sys.exit(3) diff --git a/xenserver/vswitch-xen.spec b/xenserver/vswitch-xen.spec index 42bfbf7b3..c7984ac08 100644 --- a/xenserver/vswitch-xen.spec +++ b/xenserver/vswitch-xen.spec @@ -69,6 +69,8 @@ install -m 755 xenserver/etc_xensource_scripts_vif \ $RPM_BUILD_ROOT%{_prefix}/scripts/vif install -m 755 xenserver/root_vswitch_scripts_dump-vif-details \ $RPM_BUILD_ROOT%{_prefix}/scripts/dump-vif-details +install -m 755 xenserver/usr_sbin_xen-bugtool \ + $RPM_BUILD_ROOT%{_prefix}/scripts/xen-bugtool install -m 644 \ xenserver/usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py \ $RPM_BUILD_ROOT%{_prefix}/scripts/XSFeatureVSwitch.py @@ -107,6 +109,7 @@ if [ "$1" = "1" ]; then if ! md5sum -c --status <