
Added unit test and made small modifications to the actual parser. Change-Id: I005340712ca2b17b7ca5a0517aa88f3cec7ee288
1016 lines
37 KiB
Python
1016 lines
37 KiB
Python
# 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.
|
|
|
|
import argparse
|
|
import ConfigParser
|
|
import os
|
|
import pprint
|
|
import re
|
|
|
|
import openstack_dashboard.don.ovs.common as common
|
|
|
|
|
|
def get_env(filename):
|
|
"""Returns environment dictionary created from the environment file.
|
|
|
|
:param filename: the name of the environment file.
|
|
"""
|
|
# Try read enviornment file
|
|
try:
|
|
with open(os.getcwd() + os.sep + filename, 'r') as f:
|
|
lines = f.read().splitlines()
|
|
except IOError as e:
|
|
raise(e)
|
|
|
|
# Create env dictionary based on lines that starts with 'export ...'
|
|
env = {}
|
|
for line in lines:
|
|
if line.startswith('export'):
|
|
m = re.search(r'export (.+)=(.+)', line)
|
|
if m:
|
|
key = m.group(1).replace('"', '')
|
|
val = m.group(2).replace('"', '')
|
|
env.update({key: val})
|
|
return env
|
|
|
|
|
|
# Contains all info gathered by parsing the output of commands
|
|
info = {
|
|
'vms': {},
|
|
'brctl': {},
|
|
'bridges': {
|
|
'br-ex': {'ports': {}},
|
|
'br-int': {'ports': {}},
|
|
'br-tun': {'ports': {}}
|
|
},
|
|
'floating_ips': {},
|
|
}
|
|
|
|
|
|
def add_new_command(cmds, cmd_key, cmd):
|
|
"""Add given command to the commands dictionary.
|
|
|
|
:param cmds: the commands dictionary.
|
|
:param cmd_key: the key of the dictionary where to add the command.
|
|
:param cmd: the dictionary of the command.
|
|
"""
|
|
if cmds.has_key(cmd_key):
|
|
common.error(cmd_key + ' already exists in command dictionary')
|
|
else:
|
|
cmds[cmd_key] = cmd
|
|
|
|
|
|
# TODO(abregman): create collector class and make bridges its attribute
|
|
bridges = {}
|
|
|
|
|
|
def record_linuxbridge(bridges, bridge, interface_list):
|
|
"""Add given bridge and its interfaces to the bridges dictionary.
|
|
|
|
The bridge is coming from brctl command.
|
|
|
|
:param bridge: the name of the bridge.
|
|
:param interface_list: the interfaces of the bridge.
|
|
"""
|
|
if bridges.has_key(bridge):
|
|
common.error('Bridge ' + bridge + ' repeated! Overwriting!')
|
|
bridges[bridge] = {'interfaces': interface_list}
|
|
|
|
|
|
def get_bridge_entry(bridge):
|
|
"""Returns given bridge dictionary.
|
|
|
|
:param bridge: the name of the bridge.
|
|
"""
|
|
bridges = info['bridges']
|
|
if not bridges.has_key(bridge):
|
|
common.error('Bridge ' + bridge +
|
|
' does not exist! Supported bridges: ' +
|
|
str(bridges.keys()))
|
|
return None
|
|
else:
|
|
return bridges.get(bridge)
|
|
|
|
|
|
def libvirt_instance_parser(data):
|
|
"""Parse instances from libvirt instance-*.xml files.
|
|
|
|
Each block of instance data converted to a dictionary.
|
|
|
|
Sample of data:
|
|
|
|
<uuid>31b1cfcc-ca85-48a9-a84a-8b222d377080</uuid>
|
|
<nova:name>VM1</nova:name>
|
|
<source bridge='qbrb0f5cfc8-4d'/>
|
|
<uuid>f9743f1c-caeb-4892-af83-9dc0ac757545</uuid>
|
|
<nova:name>VM2</nova:name>
|
|
<source bridge='qbr6ce314cb-a5'/>
|
|
|
|
Result:
|
|
|
|
'VM1': {'uuid': '31b1cfcc-ca85-48a9-a84a-8b222d377080',
|
|
'src_bridge': ['qbrb0f5cfc8-4d'],
|
|
'tap_dev': ['qbrb0f5cfc8-4d']
|
|
}
|
|
"""
|
|
|
|
vm_dict = info['vms']
|
|
|
|
uuid = None
|
|
name = None
|
|
src_bridge = None
|
|
|
|
for line in data:
|
|
m = re.search(r'<uuid>(.*)</uuid>', line)
|
|
if m:
|
|
uuid = m.group(1)
|
|
continue
|
|
m = re.search(r'<nova:name>(.*)</nova:name>', line)
|
|
if m:
|
|
name = m.group(1)
|
|
continue
|
|
m = re.search(r'<source bridge=(.*)/>', line)
|
|
if m:
|
|
src_bridge = m.group(1)
|
|
|
|
if not vm_dict.has_key(name):
|
|
vm_dict[name] = {}
|
|
|
|
vm_dict[name]['uuid'] = uuid
|
|
if not vm_dict[name].has_key('src_bridge'):
|
|
vm_dict[name]['src_bridge'] = []
|
|
vm_dict[name]['tap_dev'] = []
|
|
vm_dict[name]['src_bridge'].append(src_bridge)
|
|
vm_dict[name]['tap_dev'].append(src_bridge.replace('qbr', 'tap'))
|
|
|
|
return vm_dict
|
|
|
|
|
|
'''
|
|
bridge name bridge id STP enabled interfaces
|
|
qbr6ce314cb-a5 8000.9255d5550cf8 no qvb6ce314cb-a5
|
|
tap6ce314cb-a5
|
|
qbrb0f5cfc8-4d 8000.b2277f2c981b no qvbb0f5cfc8-4d
|
|
tapb0f5cfc8-4d
|
|
virbr0 8000.000000000000 yes
|
|
'''
|
|
|
|
|
|
def brctl_show_parser(parse_this):
|
|
interfaces = []
|
|
bridge = None
|
|
for line in parse_this:
|
|
m = re.search('(qbr\S+)\s+\S+\s+\S+\s+(\S+)', line)
|
|
if m:
|
|
# We already have a bridge, that means we are now lookign at the
|
|
# next bridge
|
|
if bridge:
|
|
record_linuxbridge(bridges, bridge, interfaces)
|
|
interfaces = []
|
|
bridge = m.group(1)
|
|
interfaces.append(m.group(2))
|
|
continue
|
|
m = re.search('^\s+(\S+)', line)
|
|
if m:
|
|
interfaces.append(m.group(1))
|
|
|
|
# handle the last bridge
|
|
if bridge:
|
|
record_linuxbridge(bridges, bridge, interfaces)
|
|
|
|
'''
|
|
ubuntu@ubuntu-VirtualBox:~/don$ sudo ovs-vsctl show
|
|
0fc4d93f-28e0-408a-8edb-21d5ec76b2c3
|
|
Bridge br-tun
|
|
fail_mode: secure
|
|
Port patch-int
|
|
Interface patch-int
|
|
type: patch
|
|
options: {peer=patch-tun}
|
|
Port br-tun
|
|
Interface br-tun
|
|
type: internal
|
|
Bridge br-int
|
|
fail_mode: secure
|
|
Port "tap3b74b285-71"
|
|
tag: 2
|
|
Interface "tap3b74b285-71"
|
|
type: internal
|
|
Port patch-tun
|
|
Interface patch-tun
|
|
type: patch
|
|
options: {peer=patch-int}
|
|
Port "qvob0f5cfc8-4d"
|
|
tag: 2
|
|
Interface "qvob0f5cfc8-4d"
|
|
Port "qr-77ce7d4c-d5"
|
|
tag: 1
|
|
Interface "qr-77ce7d4c-d5"
|
|
type: internal
|
|
Port "qr-56cf8a2d-27"
|
|
tag: 2
|
|
Interface "qr-56cf8a2d-27"
|
|
type: internal
|
|
Port "qvo6ce314cb-a5"
|
|
tag: 2
|
|
Interface "qvo6ce314cb-a5"
|
|
Port br-int
|
|
Interface br-int
|
|
type: internal
|
|
Port "tap9d44135a-45"
|
|
tag: 1
|
|
Interface "tap9d44135a-45"
|
|
type: internal
|
|
Bridge br-ex
|
|
Port "qg-2909632b-b8"
|
|
Interface "qg-2909632b-b8"
|
|
type: internal
|
|
Port br-ex
|
|
Interface br-ex
|
|
type: internal
|
|
Port "qg-e2fb759b-60"
|
|
Interface "qg-e2fb759b-60"
|
|
type: internal
|
|
ovs_version: "2.0.2"
|
|
'''
|
|
|
|
|
|
def ovs_vsctl_show_parser(parse_this):
|
|
bridge = None
|
|
bridge_dict = info['bridges']
|
|
for line in parse_this:
|
|
m = re.search('Bridge\s+(br-\S+)', line)
|
|
if m:
|
|
bridge = str(m.group(1))
|
|
if not bridge_dict.has_key(bridge):
|
|
common.error(
|
|
'Skipping bridge [' + bridge + ']! Supported bridges: ' +
|
|
str(bridge_dict.keys()))
|
|
bridge = None
|
|
continue
|
|
bridge_entry = bridge_dict.get(bridge)
|
|
if bridge:
|
|
m = re.search('fail_mode: (\S+)', line)
|
|
if m:
|
|
bridge_entry['fail_mode'] = m.group(1)
|
|
continue
|
|
m = re.search('Port (\S+)', line)
|
|
if m:
|
|
# the port names seem to have double quotes around them!
|
|
port = m.group(1).replace('"', '')
|
|
if not bridge_entry['ports'].has_key(port):
|
|
bridge_entry['ports'][port] = {}
|
|
port_entry = bridge_entry['ports'][port]
|
|
continue
|
|
m = re.search('tag: (\d+)', line)
|
|
if m:
|
|
port_entry['tag'] = m.group(1)
|
|
continue
|
|
m = re.search('Interface (\S+)', line)
|
|
if m:
|
|
# the interface names seem to have double quotes around them!
|
|
interface = m.group(1).replace('"', '')
|
|
if not port_entry.has_key('interfaces'):
|
|
port_entry['interfaces'] = {}
|
|
port_entry['interfaces'][interface] = {}
|
|
interface_entry = port_entry['interfaces'][interface]
|
|
continue
|
|
m = re.search('type: (\S+)', line)
|
|
if m:
|
|
interface_entry['type'] = m.group(1)
|
|
continue
|
|
m = re.search('options: {(\S+)}', line)
|
|
if m:
|
|
options = m.group(1)
|
|
interface_entry['options'] = options
|
|
continue
|
|
|
|
'''
|
|
OFPT_FEATURES_REPLY (xid=0x2): dpid:00008207ee8eee4d
|
|
n_tables:254, n_buffers:256
|
|
capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP
|
|
actions: OUTPUT SET_VLAN_VID SET_VLAN_PCP STRIP_VLAN SET_DL_SRC SET_DL_DST \
|
|
SET_NW_SRC SET_NW_DST SET_NW_TOS SET_TP_SRC SET_TP_DST ENQUEUE
|
|
4(patch-tun): addr:e2:ce:31:60:94:e0
|
|
config: 0
|
|
state: 0
|
|
speed: 0 Mbps now, 0 Mbps max
|
|
5(tap9d44135a-45): addr:00:00:00:00:00:00
|
|
config: PORT_DOWN
|
|
state: LINK_DOWN
|
|
speed: 0 Mbps now, 0 Mbps max
|
|
6(qr-77ce7d4c-d5): addr:00:00:00:00:00:00
|
|
config: PORT_DOWN
|
|
state: LINK_DOWN
|
|
speed: 0 Mbps now, 0 Mbps max
|
|
7(tap3b74b285-71): addr:00:00:00:00:00:00
|
|
config: PORT_DOWN
|
|
state: LINK_DOWN
|
|
speed: 0 Mbps now, 0 Mbps max
|
|
8(qr-56cf8a2d-27): addr:00:00:00:00:00:00
|
|
config: PORT_DOWN
|
|
state: LINK_DOWN
|
|
speed: 0 Mbps now, 0 Mbps max
|
|
9(qvob0f5cfc8-4d): addr:7a:82:4f:4e:a0:ab
|
|
config: 0
|
|
state: 0
|
|
current: 10GB-FD COPPER
|
|
speed: 10000 Mbps now, 0 Mbps max
|
|
10(qvo6ce314cb-a5): addr:42:92:2a:95:28:ed
|
|
config: 0
|
|
state: 0
|
|
current: 10GB-FD COPPER
|
|
speed: 10000 Mbps now, 0 Mbps max
|
|
LOCAL(br-int): addr:82:07:ee:8e:ee:4d
|
|
config: 0
|
|
state: 0
|
|
speed: 0 Mbps now, 0 Mbps max
|
|
OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0
|
|
'''
|
|
|
|
|
|
def ovs_ofctl_show_br_parser(bridge, parse_this):
|
|
bridge_dict = info['bridges']
|
|
if not bridge_dict.has_key(bridge):
|
|
common.error('Skipping bridge [' + bridge + ']! Supported bridges: ' + str(bridge_dict.keys()))
|
|
return
|
|
bridge_entry = bridge_dict.get(bridge)
|
|
pprint.pprint(bridge_entry)
|
|
|
|
for line in parse_this:
|
|
m = re.search('(\d+)\((\S+)\):\s+addr:(\S+)', line)
|
|
if m:
|
|
port_id = m.group(1)
|
|
port = m.group(2)
|
|
port_mac = m.group(3)
|
|
if not bridge_entry['ports'].has_key(port):
|
|
bridge_entry['ports'][port] = {}
|
|
port_entry = bridge_entry['ports'][port]
|
|
port_entry['id'] = port_id
|
|
port_entry['mac'] = port_mac
|
|
continue
|
|
|
|
m = re.search('(\w+)\((\S+)\):\s+addr:(\S+)', line)
|
|
if m:
|
|
port_id = m.group(1)
|
|
port = m.group(2)
|
|
port_mac = m.group(3)
|
|
if not bridge_entry['ports'].has_key(port):
|
|
bridge_entry['ports'][port] = {}
|
|
port_entry = bridge_entry['ports'][port]
|
|
port_entry['id'] = port_id
|
|
port_entry['mac'] = port_mac
|
|
|
|
# These three are all wrappers for each of the three bridges
|
|
|
|
|
|
def ovs_ofctl_show_br_int_parser(parse_this):
|
|
ovs_ofctl_show_br_parser('br-int', parse_this)
|
|
|
|
|
|
def ovs_ofctl_show_br_ex_parser(parse_this):
|
|
ovs_ofctl_show_br_parser('br-ex', parse_this)
|
|
|
|
|
|
def ovs_ofctl_show_br_tun_parser(parse_this):
|
|
ovs_ofctl_show_br_parser('br-tun', parse_this)
|
|
|
|
'''
|
|
+--------------------------------------+-------+--------+------------+-------------+--------------------------------------------------------+
|
|
| ID | Name | Status | Task State | Power State | Networks |
|
|
+--------------------------------------+-------+--------+------------+-------------+--------------------------------------------------------+
|
|
| 31b1cfcc-ca85-48a9-a84a-8b222d377080 | VM1 | ACTIVE | - | Running | private=10.0.2.3 |
|
|
| f9743f1c-caeb-4892-af83-9dc0ac757545 | VM2 | ACTIVE | - | Running | private=10.0.2.4 |
|
|
| 83b547b9-9578-4840-997a-5aa1c4e829b0 | VM3-1 | ACTIVE | - | Running | private2=10.0.3.3 |
|
|
| 17b4685e-5cbe-4dd1-862a-6f89c191e1e7 | VM3-2 | ACTIVE | - | Running | private2=10.0.3.4 |
|
|
| ee4952a3-0700-42ea-aab3-7503bc9d87e2 | VM4 | ACTIVE | - | Running | private2=10.0.3.5; public=172.24.4.4; private=10.0.2.5 |
|
|
+--------------------------------------+-------+--------+------------+-------------+--------------------------------------------------------+
|
|
'''
|
|
|
|
|
|
def nova_list_parser(parse_this):
|
|
vm_dict = info['vms']
|
|
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line) or \
|
|
re.search('Networks', line):
|
|
continue
|
|
parts = line.split('|')
|
|
parts = [x.strip() for x in parts]
|
|
|
|
vm = parts[2]
|
|
networks = parts[6].split(';')
|
|
networks = [x.strip() for x in networks]
|
|
|
|
if not vm_dict.has_key(vm):
|
|
vm_dict[vm] = {'interfaces': {}}
|
|
|
|
for entry in networks:
|
|
# excluding ipv6 ip
|
|
if len(entry.split(',')) > 1:
|
|
network = entry.split('=')[0]
|
|
ip = filter(lambda a: re.search("(\d+\.\d+\.\d+\.\d+)", a) is not
|
|
None, entry.split('=')[1].split(','))[0].strip()
|
|
else:
|
|
(network, ip) = entry.split(',')[0].split('=')
|
|
vm_dict[vm]['interfaces'][ip] = {'network': network}
|
|
|
|
pass
|
|
|
|
|
|
'''
|
|
+--------------------------------------+------+-------------------+-----------------------------------------------------------------------------------+
|
|
| id | name | mac_address | fixed_ips |
|
|
+--------------------------------------+------+-------------------+-----------------------------------------------------------------------------------+
|
|
| 1dd820b1-98bd-4f39-b1ab-e89ecc67ae43 | | fa:16:3e:0f:36:26 | {"subnet_id": "75ae4ce8-495d-4f53-93d1-bf98e55d6658", "ip_address": "172.24.4.4"} |
|
|
| 1f73af79-fa69-4433-bcab-16d7a0bc2607 | | fa:16:3e:dc:c8:de | {"subnet_id": "dbc9717f-5a08-48bb-92e2-ed2da443541b", "ip_address": "10.0.3.1"} |
|
|
| 2909632b-b8a3-436b-aabd-9868d0c1051e | | fa:16:3e:af:95:a9 | {"subnet_id": "75ae4ce8-495d-4f53-93d1-bf98e55d6658", "ip_address": "172.24.4.2"} |
|
|
| 3b74b285-71d0-4311-8a69-2b032eebbe13 | | fa:16:3e:70:09:45 | {"subnet_id": "1083b740-45ce-49be-b603-73cbc26af5d7", "ip_address": "10.0.2.2"} |
|
|
| 56cf8a2d-27b7-4eab-a334-349c70520868 | | fa:16:3e:8a:ce:cb | {"subnet_id": "1083b740-45ce-49be-b603-73cbc26af5d7", "ip_address": "10.0.2.1"} |
|
|
| 6ce314cb-a599-4af8-8187-bdb0bfa88809 | | fa:16:3e:83:b1:60 | {"subnet_id": "1083b740-45ce-49be-b603-73cbc26af5d7", "ip_address": "10.0.2.4"} |
|
|
| 77ce7d4c-d5b9-4669-b23c-b0d9ee5f58c8 | | fa:16:3e:a6:de:15 | {"subnet_id": "531f1674-2b46-4ad7-9d73-4c41d215cc99", "ip_address": "10.0.0.1"} |
|
|
| 9c34adc0-c655-4b00-89ba-ca65def56fe0 | | fa:16:3e:a1:e7:f5 | {"subnet_id": "dbc9717f-5a08-48bb-92e2-ed2da443541b", "ip_address": "10.0.3.4"} |
|
|
| 9d44135a-4551-4448-9c80-d211b023c3eb | | fa:16:3e:80:83:c9 | {"subnet_id": "531f1674-2b46-4ad7-9d73-4c41d215cc99", "ip_address": "10.0.0.2"} |
|
|
| b0f5cfc8-4da0-42ad-8c18-6f29870bfb2a | | fa:16:3e:ae:a2:17 | {"subnet_id": "1083b740-45ce-49be-b603-73cbc26af5d7", "ip_address": "10.0.2.3"} |
|
|
| c03437a8-8a44-4615-b160-e1ef227d63c5 | | fa:16:3e:7f:b6:a5 | {"subnet_id": "dbc9717f-5a08-48bb-92e2-ed2da443541b", "ip_address": "10.0.3.5"} |
|
|
| cb7d8a29-8140-4ed0-a1c7-03cbf0be0c5b | | fa:16:3e:33:ee:b1 | {"subnet_id": "1083b740-45ce-49be-b603-73cbc26af5d7", "ip_address": "10.0.2.5"} |
|
|
| e2fb759b-602a-4fcd-8674-e8f5fe297dbc | | fa:16:3e:ea:47:b5 | {"subnet_id": "75ae4ce8-495d-4f53-93d1-bf98e55d6658", "ip_address": "172.24.4.3"} |
|
|
| e4f25d71-5684-4ccc-8114-2465a84ecc58 | | fa:16:3e:90:c7:d3 | {"subnet_id": "dbc9717f-5a08-48bb-92e2-ed2da443541b", "ip_address": "10.0.3.2"} |
|
|
| f57aa80e-2ef3-4031-a0a4-bc12d2445687 | | fa:16:3e:2e:6e:91 | {"subnet_id": "dbc9717f-5a08-48bb-92e2-ed2da443541b", "ip_address": "10.0.3.3"} |
|
|
+--------------------------------------+------+-------------------+-----------------------------------------------------------------------------------+
|
|
'''
|
|
|
|
|
|
def neutron_port_list_parser(parse_this):
|
|
tap_to_ip = {}
|
|
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line) or \
|
|
re.search('fixed_ips', line):
|
|
continue
|
|
|
|
parts = line.split('|')
|
|
parts = [x.strip() for x in parts]
|
|
|
|
tap = parts[1][:11]
|
|
# ip = parts[4].split(':')[-1].replace('}', '')
|
|
m = re.search('"ip_address": "(\S+)"', parts[4])
|
|
if m:
|
|
ip = m.group(1)
|
|
tap_to_ip[tap] = ip
|
|
|
|
info['tap_to_ip'] = tap_to_ip
|
|
pass
|
|
|
|
'''
|
|
+--------------------------------------+---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------+
|
|
| id | name | external_gateway_info | distributed | ha |
|
|
+--------------------------------------+---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------+
|
|
| 8c981cdb-c19f-47c1-8149-f85a506c486c | router1 | {"network_id": "640ece56-c6dc-4868-8e7a-12547508098a", "enable_snat": true, "external_fixed_ips": [{"subnet_id": "75ae4ce8-495d-4f53-93d1-bf98e55d6658", "ip_address": "172.24.4.2"}]} | False | False |
|
|
| ac41aab2-f9c3-4a06-8eef-f909ee1e6e50 | router | {"network_id": "640ece56-c6dc-4868-8e7a-12547508098a", "enable_snat": true, "external_fixed_ips": [{"subnet_id": "75ae4ce8-495d-4f53-93d1-bf98e55d6658", "ip_address": "172.24.4.3"}]} | False | False |
|
|
+--------------------------------------+---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------+
|
|
'''
|
|
|
|
|
|
def neutron_router_list_parser(parse_this):
|
|
routers = {}
|
|
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line) or \
|
|
re.search('external_gateway_info', line):
|
|
continue
|
|
|
|
parts = line.split('|')
|
|
parts = [x.strip() for x in parts]
|
|
|
|
router_id = parts[1]
|
|
name = parts[2]
|
|
|
|
network_id = 'unknown'
|
|
m = re.search('"network_id":\s+"(\S+)"', parts[3])
|
|
if m:
|
|
network_id = m.group(1)
|
|
|
|
ip_address = 'x.x.x.x'
|
|
m = re.search('"ip_address":\s+"(\d+\.\d+\.\d+\.\d+)"', parts[3])
|
|
if m:
|
|
ip_address = m.group(1)
|
|
|
|
routers[name] = {'id': router_id,
|
|
'ip_address': ip_address,
|
|
'network_id': network_id,
|
|
}
|
|
|
|
info['routers'] = routers
|
|
|
|
# now add some more commands to get further information for
|
|
# l3-agents which run in different namespaces
|
|
for router in info['routers'].keys():
|
|
uuid = info['routers'][router]['id']
|
|
namespace = 'qrouter-' + uuid
|
|
|
|
cmd_key = 'netns_' + namespace
|
|
cmd = {
|
|
'cmd': 'echo namespace: ' + namespace + '; echo "sudo ip netns exec ' + namespace + ' ip a" > /tmp/don.bash; bash /tmp/don.bash',
|
|
'help': 'Collect namespace info for l3-agent',
|
|
'shell': True,
|
|
'output': None,
|
|
'order': 100,
|
|
'parser': ip_namespace_qrouter_parser,
|
|
}
|
|
add_new_command(commands, cmd_key, cmd)
|
|
|
|
|
|
def ip_namespace_qrouter_parser(parse_this):
|
|
nm_dict = info['namespaces']
|
|
|
|
qr_intf = None
|
|
qg_intf = None
|
|
ip = None
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line):
|
|
continue
|
|
m = re.search('^namespace: (\S+)', line)
|
|
if m:
|
|
namespace = m.group(1)
|
|
continue
|
|
|
|
m = re.search('^\d+: (qr-\S+):', line)
|
|
if m:
|
|
qr_intf = m.group(1)
|
|
continue
|
|
|
|
m = re.search('^\d+: (qg-\S+):', line)
|
|
if m:
|
|
qg_intf = m.group(1)
|
|
continue
|
|
|
|
m = re.search('inet (\d+\.\d+\.\d+\.\d+/\d+)', line)
|
|
if m:
|
|
ip = m.group(1)
|
|
|
|
if not nm_dict[namespace].has_key('interfaces'):
|
|
nm_dict[namespace] = {'interfaces': {}}
|
|
|
|
if qg_intf:
|
|
nm_dict[namespace]['interfaces'][qg_intf] = ip
|
|
elif qr_intf:
|
|
nm_dict[namespace]['interfaces'][qr_intf] = ip
|
|
else:
|
|
continue
|
|
|
|
qr_intf = None
|
|
qg_intf = None
|
|
ip = None
|
|
pass
|
|
|
|
|
|
def ip_namespace_qdhcp_parser(parse_this):
|
|
nm_dict = info['namespaces']
|
|
|
|
tap_intf = None
|
|
ip = None
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line):
|
|
continue
|
|
m = re.search('^namespace: (\S+)', line)
|
|
if m:
|
|
namespace = m.group(1)
|
|
continue
|
|
|
|
m = re.search('^\d+: (tap\S+):', line)
|
|
if m:
|
|
tap_intf = m.group(1)
|
|
|
|
m = re.search('inet (\d+\.\d+\.\d+\.\d+/\d+)', line)
|
|
if m:
|
|
ip = m.group(1)
|
|
|
|
if not nm_dict[namespace].has_key('interfaces'):
|
|
nm_dict[namespace] = {'interfaces': {}}
|
|
|
|
if tap_intf:
|
|
nm_dict[namespace]['interfaces'][tap_intf] = ip
|
|
|
|
tap_intf = None
|
|
ip = None
|
|
pass
|
|
|
|
|
|
'''
|
|
+--------------------------------------+----------+----------------------------------------------------------+
|
|
| id | name | subnets |
|
|
+--------------------------------------+----------+----------------------------------------------------------+
|
|
| 0a355cf0-00d0-45e1-9a3a-9aca436510d5 | private2 | 8393a2da-09dd-46e8-a26f-caf9f12c48f5 10.0.3.0/24 |
|
|
| 3b4ddfcb-49b8-46ae-9ecd-cb4f9b1830fc | public | 2dd78cb6-eb90-44ea-82b0-bbdb7316edb2 172.24.4.0/24 |
|
|
| | | 304ce342-18fe-4b4a-aa49-f5c7e5e31b2a 2001:db8::/64 |
|
|
| 4b7a42e8-cc16-411c-b932-989106c2f934 | private1 | cc580da4-0b61-4982-ae7b-d2d5c441b1d7 10.0.2.0/24 |
|
|
| bfedebe8-c436-4056-8d12-1d2f7e62e8ec | private | 4deed2ad-e184-43a9-8cc7-4493aa07f78f fdfd:57f1:b2ba::/64 |
|
|
| | | 8e2c5cfd-fbc1-4fe0-9f5e-f0b0dc070fb8 10.0.0.0/24 |
|
|
+--------------------------------------+----------+----------------------------------------------------------+
|
|
'''
|
|
|
|
|
|
def neutron_net_list_parser(parse_this):
|
|
networks = {}
|
|
|
|
ip = 'unknown'
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line) or re.search('subnets', line):
|
|
continue
|
|
|
|
# Skip IPv6 for the time being
|
|
m = re.search('^\| (\S+) \| (\S+)\s+\| \S+ (\S+)', line)
|
|
if m:
|
|
network_id = m.group(1)
|
|
name = m.group(2)
|
|
possible_ip = m.group(3)
|
|
if re.search('\.', possible_ip):
|
|
ip = possible_ip
|
|
networks[network_id] = {'name': name,
|
|
'ip': ip
|
|
}
|
|
m = re.search('^\|\s+\|\s+\| \S+ (\S+)', line)
|
|
if m:
|
|
possible_ip = m.group(1)
|
|
if re.search('\.', possible_ip):
|
|
ip = possible_ip
|
|
networks[network_id] = {'name': name,
|
|
'ip': ip
|
|
}
|
|
ip = 'Unknown'
|
|
|
|
info['networks'] = networks
|
|
|
|
# now add some more commands to get further information for
|
|
# dhcp agents which run in different namespaces
|
|
for network_id in networks.keys():
|
|
# There is no dhcp agent run for public network
|
|
if networks[network_id]['name'] == 'public':
|
|
continue
|
|
|
|
namespace = 'qdhcp-' + network_id
|
|
|
|
cmd_key = 'netns_' + namespace
|
|
cmd = {
|
|
'cmd': 'echo namespace: ' + namespace + '; echo "sudo ip netns exec ' + namespace + ' ip a" > /tmp/don.bash; bash /tmp/don.bash',
|
|
'help': 'Collect namespace info for dhcp-agent',
|
|
'shell': True,
|
|
'output': None,
|
|
'order': 110,
|
|
'parser': ip_namespace_qdhcp_parser,
|
|
}
|
|
add_new_command(commands, cmd_key, cmd)
|
|
|
|
|
|
'''
|
|
qdhcp-d5357ad8-df8b-4f19-8433-9db13304e4b2
|
|
qrouter-ac41aab2-f9c3-4a06-8eef-f909ee1e6e50
|
|
qdhcp-49be53de-33ed-480a-a06e-6e77c8f887dc
|
|
qrouter-8c981cdb-c19f-47c1-8149-f85a506c486c
|
|
qdhcp-82b0e328-4530-495e-a43f-238ef7a53d62
|
|
'''
|
|
|
|
|
|
def ip_netns_parser(parse_this):
|
|
namespaces = {}
|
|
|
|
for line in parse_this:
|
|
if re.search('^q', line):
|
|
namespaces[line] = {}
|
|
|
|
info['namespaces'] = namespaces
|
|
|
|
|
|
def dummy_parser(parse_this):
|
|
common.debug('Dummy Parser :-)')
|
|
pass
|
|
|
|
|
|
def floating_ip_list_parser(parse_this):
|
|
floating_ips = {}
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line) or re.search('Pool', line):
|
|
continue
|
|
parts = line.split('|')
|
|
parts = [x.strip() for x in parts]
|
|
floating_ip = parts[2]
|
|
vm_id = parts[3]
|
|
pool = parts[5]
|
|
# ignore floating ips which is not assigned to any vms
|
|
if vm_id != '-':
|
|
floating_ips.update(
|
|
{vm_id: {'floating_ip': floating_ip, 'pool': pool}})
|
|
info['floating_ips'] = floating_ips
|
|
|
|
|
|
# static commands whose output have info that help us diagnose
|
|
commands = {
|
|
'nova_list':
|
|
{
|
|
'cmd': ['nova', 'list'],
|
|
'help': 'Collect list of VMs from nova',
|
|
'env': True,
|
|
'output': None,
|
|
'order': 1,
|
|
'parser': nova_list_parser,
|
|
},
|
|
'libvirt_instance':
|
|
{
|
|
'cmd': 'cat /etc/libvirt/qemu/instance-*.xml | egrep -e "<uuid>" -e "nova:name" -e "source bridge"',
|
|
'help': 'Collect some info from the launched VMs',
|
|
'sudo': True,
|
|
'shell': True,
|
|
'output': None,
|
|
'order': 2,
|
|
'parser': libvirt_instance_parser,
|
|
},
|
|
'neutron_port_list':
|
|
{
|
|
'cmd': ['neutron', 'port-list'],
|
|
'help': 'Collect neutron configured ports',
|
|
'env': True,
|
|
'output': None,
|
|
'order': 3,
|
|
'parser': neutron_port_list_parser,
|
|
},
|
|
'neutron_router_list':
|
|
{
|
|
'cmd': ['neutron', 'router-list'],
|
|
'help': 'Collect neutron configured routers',
|
|
'env': True,
|
|
'output': None,
|
|
'order': 4,
|
|
'parser': neutron_router_list_parser,
|
|
},
|
|
'neutron_net_list':
|
|
{
|
|
'cmd': ['neutron', 'net-list'],
|
|
'help': 'Collect neutron configured networks',
|
|
'env': True,
|
|
'output': None,
|
|
'order': 5,
|
|
'parser': neutron_net_list_parser,
|
|
},
|
|
'ip_netns':
|
|
{
|
|
'cmd': ['ip', 'netns'],
|
|
'help': 'Collect network namespaces',
|
|
'output': None,
|
|
'order': 6,
|
|
'parser': ip_netns_parser,
|
|
},
|
|
|
|
'brctl_show':
|
|
{
|
|
'cmd': ['brctl', 'show'],
|
|
'help': 'Collect information about bridges (linuxbridge) configured',
|
|
'output': None,
|
|
'order': 10,
|
|
'parser': brctl_show_parser,
|
|
},
|
|
'ovs_appctl_fdb_show_br_ex':
|
|
{
|
|
'cmd': ['ovs-appctl', 'fdb/show', 'br-ex'],
|
|
'help': 'Collect mac data base for bridge br-ex',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 20,
|
|
'parser': None,
|
|
},
|
|
'ovs_appctl_fdb_show_br_int':
|
|
{
|
|
'cmd': ['ovs-appctl', 'fdb/show', 'br-int'],
|
|
'help': 'Collect mac data base for ovs bridge br-int',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 21,
|
|
'parser': None,
|
|
},
|
|
'ovs_appctl_fdb_show_br_tun':
|
|
{
|
|
'cmd': ['ovs-appctl', 'fdb/show', 'br-tun'],
|
|
'help': 'Collect mac data base for ovs bridge br-tun',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 22,
|
|
'parser': None,
|
|
},
|
|
'ovs_vsctl_show':
|
|
{
|
|
'cmd': ['ovs-vsctl', 'show'],
|
|
'help': 'Collect ovs bridge info',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 30,
|
|
'parser': ovs_vsctl_show_parser,
|
|
},
|
|
'ovs_ofctl_show_br_ex':
|
|
{
|
|
'cmd': ['ovs-ofctl', 'show', 'br-ex'],
|
|
'help': 'Collect openflow information for ovs bridge br-ex',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 40,
|
|
'parser': ovs_ofctl_show_br_ex_parser,
|
|
},
|
|
'ovs_ofctl_show_br_int':
|
|
{
|
|
'cmd': ['ovs-ofctl', 'show', 'br-int'],
|
|
'help': 'Collect openflow information for ovs bridge br-int',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 41,
|
|
'parser': ovs_ofctl_show_br_int_parser,
|
|
},
|
|
'ovs_ofctl_show_br_tun':
|
|
{
|
|
'cmd': ['ovs-ofctl', 'show', 'br-tun'],
|
|
'help': 'Collect openflow information for ovs bridge br-tun',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 42,
|
|
'parser': ovs_ofctl_show_br_tun_parser,
|
|
},
|
|
'ovs_ofctl_dump_flows_br_ex':
|
|
{
|
|
'cmd': ['ovs-ofctl', 'dump-flows', 'br-ex'],
|
|
'help': 'Collect openflow flow table information for ovs bridge br-ex',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 50,
|
|
'parser': None,
|
|
},
|
|
'ovs_ofctl_dump_flows_br_int':
|
|
{
|
|
'cmd': ['ovs-ofctl', 'dump-flows', 'br-int'],
|
|
'help': 'Collect openflow flow table information for ovs bridge br-int',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 51,
|
|
'parser': None,
|
|
},
|
|
'ovs_ofctl_dump_flows_br_tun':
|
|
{
|
|
'cmd': ['ovs-ofctl', 'dump-flows', 'br-tun'],
|
|
'help': 'Collect openflow flow table information for ovs bridge br-tun',
|
|
'sudo': True,
|
|
'output': None,
|
|
'order': 52,
|
|
'parser': None,
|
|
},
|
|
'instance_floating_ip_list':
|
|
{
|
|
'cmd': ['nova', 'floating-ip-list'],
|
|
'help': 'Collect floating ip information for instances',
|
|
'env': True,
|
|
'output': None,
|
|
'order': 53,
|
|
'parser': floating_ip_list_parser,
|
|
},
|
|
|
|
}
|
|
|
|
|
|
def check_args():
|
|
parser = argparse.ArgumentParser(description='Runs commands, collects, and parses output',
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
parser.add_argument('--debug', dest='debug', help='Enable debugging',
|
|
default=True, action='store_true')
|
|
parser.add_argument('--info_file', dest='info_file',
|
|
help='Info will be stored in JSON format in this file',
|
|
default="don.json", type=str)
|
|
args = parser.parse_args()
|
|
|
|
common.settings['debug'] = args.debug
|
|
common.settings['info_file'] = args.info_file
|
|
|
|
|
|
def all_commands_executed(commands):
|
|
for cmd in commands.keys():
|
|
if commands[cmd]['parser']:
|
|
done = commands[cmd].get('done', False)
|
|
if done is False:
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_vm_info_from_compute(cmd, my_env):
|
|
output = common.execute_cmd(['nova', 'hypervisor-list'],
|
|
sudo=False, shell=False, env=my_env).split('\n')
|
|
compute_list = get_hypervisor(output)
|
|
vm_info = []
|
|
compute_creds = common.get_vm_credentials()
|
|
for node in compute_list:
|
|
creds = compute_creds.get('hypervisor').get(
|
|
node, compute_creds.get('hypervisor')['default'])
|
|
ssh = common.connect_to_box(node, creds['username'], creds['password'])
|
|
(stdin, out, err) = ssh.exec_command('sudo ' + cmd)
|
|
vm_info.extend(out.read().splitlines())
|
|
ssh.close()
|
|
return vm_info
|
|
|
|
|
|
def exec_on_remote(cmd):
|
|
node_details = common.get_vm_credentials()
|
|
creds = node_details.get('network')
|
|
# print "sudo "+cmd
|
|
ssh = common.connect_to_box(creds['hostname'], creds[
|
|
'username'], creds['password'])
|
|
(stdin, out, err) = ssh.exec_command(cmd)
|
|
if len(err.read()):
|
|
return []
|
|
return out.read().splitlines()
|
|
|
|
|
|
def get_hypervisor(parse_this):
|
|
hypervisor = []
|
|
for line in parse_this:
|
|
if re.search('^\+', line) or re.search('^$', line) or re.search('Hypervisor hostname', line):
|
|
continue
|
|
parts = line.split('|')
|
|
parts = [x.strip() for x in parts]
|
|
name = parts[2]
|
|
hypervisor.append(name)
|
|
return hypervisor
|
|
|
|
|
|
def load_config():
|
|
"""Loads configuration."""
|
|
don_config = ConfigParser.ConfigParser()
|
|
try:
|
|
don_config.read('/etc/don/don.conf')
|
|
except Exception as e:
|
|
raise(e.value)
|
|
return don_config.get('DEFAULT', 'deployment_type')
|
|
|
|
|
|
def main():
|
|
deployment_type = load_config()
|
|
|
|
my_env = os.environ.copy()
|
|
my_env.update(get_env('admin-openrc.sh'))
|
|
|
|
check_args()
|
|
|
|
iteration = 0
|
|
# Parser of any specific command might add more commands to be executed.
|
|
# Hence continue in a loop.
|
|
while True:
|
|
if (all_commands_executed(commands) or iteration >= 10):
|
|
break
|
|
iteration += 1
|
|
common.status_update('Iteration: ' + str(iteration))
|
|
|
|
sorted_keys = sorted(commands.items(), key=lambda (k, v): v['order'])
|
|
for (cmd, dontcare) in sorted_keys:
|
|
# Only collect stuff for which we have written a parser
|
|
if commands[cmd]['parser']:
|
|
if commands[cmd].get('done', False):
|
|
continue
|
|
if commands[cmd].has_key('help'):
|
|
common.status_update(commands[cmd]['help'])
|
|
shell = commands[cmd].get('shell', False)
|
|
env = None
|
|
if commands[cmd].get('env', False):
|
|
env = my_env
|
|
sudo = commands[cmd].get('sudo', False)
|
|
if deployment_type == 'multinode':
|
|
# handling for network node
|
|
if cmd.startswith('netns_'):
|
|
commands[cmd]['output'] = exec_on_remote(
|
|
commands[cmd]['cmd'])
|
|
if cmd == 'libvirt_instance':
|
|
commands[cmd][
|
|
'output'] = get_vm_info_from_compute(
|
|
commands[cmd]['cmd'], my_env)
|
|
print(commands[cmd]['output'])
|
|
else:
|
|
commands[cmd]['output'] = common.execute_cmd(
|
|
commands[cmd]['cmd'], sudo=sudo,
|
|
shell=shell, env=env).split('\n')
|
|
else:
|
|
commands[cmd]['output'] = common.execute_cmd(
|
|
commands[cmd]['cmd'],
|
|
sudo=sudo, shell=shell, env=env).split('\n')
|
|
commands[cmd]['parser'](commands[cmd]['output'])
|
|
commands[cmd]['done'] = True
|
|
|
|
common.debug('============= COMMANDS =============')
|
|
common.status_update(
|
|
'Writing collected info into ' + common.settings['info_file'])
|
|
common.dump_json(info, common.settings['info_file'])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|