#!/usr/bin/env python # # Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Dump the state of the world for post mortem.""" import argparse import datetime from distutils import spawn import fnmatch import os import os.path import subprocess import sys def get_options(): parser = argparse.ArgumentParser( description='Dump world state for debugging') parser.add_argument('-d', '--dir', default='.', help='Output directory for worlddump') parser.add_argument('-n', '--name', default='', help='Additional name to tag into file') return parser.parse_args() def filename(dirname, name=""): now = datetime.datetime.utcnow() fmt = "worlddump-%Y-%m-%d-%H%M%S" if name: fmt += "-" + name fmt += ".txt" return os.path.join(dirname, now.strftime(fmt)) def warn(msg): print "WARN: %s" % msg def _dump_cmd(cmd): print cmd print "-" * len(cmd) print try: subprocess.check_call(cmd, shell=True) print except subprocess.CalledProcessError as e: print "*** Failed to run '%(cmd)s': %(err)s" % {'cmd': cmd, 'err': e} def _find_cmd(cmd): if not spawn.find_executable(cmd): print "*** %s not found: skipping" % cmd return False return True def _header(name): print print name print "=" * len(name) print # This method gets a max openflow version supported by openvswitch. # For example 'ovs-ofctl --version' displays the following: # # ovs-ofctl (Open vSwitch) 2.0.2 # Compiled Dec 9 2015 14:08:08 # OpenFlow versions 0x1:0x4 # # The above shows that openvswitch supports from OpenFlow11 to OpenFlow13. # This method gets max version searching 'OpenFlow versions 0x1:0x'. # And return a version value converted to an integer type. def _get_ofp_version(): process = subprocess.Popen(['ovs-ofctl', '--version'], stdout=subprocess.PIPE) stdout, _ = process.communicate() find_str = 'OpenFlow versions 0x1:0x' offset = stdout.find(find_str) return int(stdout[offset + len(find_str):-1]) - 1 def disk_space(): # the df output _header("File System Summary") dfraw = os.popen("df -Ph").read() df = [s.split() for s in dfraw.splitlines()] for fs in df: try: if int(fs[4][:-1]) > 95: warn("Device %s (%s) is %s full, might be an issue" % ( fs[0], fs[5], fs[4])) except ValueError: # if it doesn't look like an int, that's fine pass print dfraw def ebtables_dump(): tables = ['filter', 'nat', 'broute'] _header("EB Tables Dump") if not _find_cmd('ebtables'): return for table in tables: _dump_cmd("sudo ebtables -t %s -L" % table) def iptables_dump(): tables = ['filter', 'nat', 'mangle'] _header("IP Tables Dump") for table in tables: _dump_cmd("sudo iptables --line-numbers -L -nv -t %s" % table) def _netns_list(): process = subprocess.Popen(['ip', 'netns'], stdout=subprocess.PIPE) stdout, _ = process.communicate() return stdout.split() def network_dump(): _header("Network Dump") _dump_cmd("brctl show") _dump_cmd("arp -n") ip_cmds = ["addr", "link", "route"] for cmd in ip_cmds + ['netns']: _dump_cmd("ip %s" % cmd) for netns_ in _netns_list(): for cmd in ip_cmds: args = {'netns': netns_, 'cmd': cmd} _dump_cmd('sudo ip netns exec %(netns)s ip %(cmd)s' % args) def ovs_dump(): _header("Open vSwitch Dump") # NOTE(cdent): If we're not using neutron + ovs these commands # will not be present so if not _find_cmd('ovs-vsctl'): return # NOTE(ihrachys): worlddump is used outside of devstack context (f.e. in # grenade), so there is no single place to determine the bridge names from. # Hardcode for now. bridges = ('br-int', 'br-tun', 'br-ex') ofctl_cmds = ('show', 'dump-ports-desc', 'dump-ports', 'dump-flows') ofp_max = _get_ofp_version() vers = 'OpenFlow10' for i in range(ofp_max + 1): vers += ',OpenFlow1' + str(i) _dump_cmd("sudo ovs-vsctl show") for ofctl_cmd in ofctl_cmds: for bridge in bridges: args = {'vers': vers, 'cmd': ofctl_cmd, 'bridge': bridge} _dump_cmd("sudo ovs-ofctl --protocols=%(vers)s %(cmd)s %(bridge)s" % args) def process_list(): _header("Process Listing") _dump_cmd("ps axo " "user,ppid,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,args") def compute_consoles(): _header("Compute consoles") for root, dirnames, filenames in os.walk('/opt/stack'): for filename in fnmatch.filter(filenames, 'console.log'): fullpath = os.path.join(root, filename) _dump_cmd("sudo cat %s" % fullpath) def guru_meditation_report(): _header("nova-compute Guru Meditation Report") try: subprocess.check_call(["pgrep","nova-compute"]) except subprocess.CalledProcessError: print "Skipping as nova-compute does not appear to be running" return _dump_cmd("kill -s USR2 `pgrep nova-compute`") print "guru meditation report in nova-compute log" def main(): opts = get_options() fname = filename(opts.dir, opts.name) print "World dumping... see %s for details" % fname sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) with open(fname, 'w') as f: os.dup2(f.fileno(), sys.stdout.fileno()) disk_space() process_list() network_dump() ovs_dump() iptables_dump() ebtables_dump() compute_consoles() guru_meditation_report() if __name__ == '__main__': try: sys.exit(main()) except KeyboardInterrupt: sys.exit(1)