summaryrefslogtreecommitdiffstats
path: root/apex
diff options
context:
space:
mode:
authorFeng Pan <fpan@redhat.com>2017-09-13 18:25:34 +0000
committerGerrit Code Review <gerrit@opnfv.org>2017-09-13 18:25:34 +0000
commit7b3a8455baa35bdde4d7e078d88f9b17a6df9195 (patch)
tree0d0617382f42d30d4bb0a9a99248cb100006554c /apex
parent071de3a4a11326d5f0c371a2ebeed703f1a40980 (diff)
parentcb606f45e3852432787ed895dc55665caa950161 (diff)
Merge "Migrates clean to python"
Diffstat (limited to 'apex')
-rw-r--r--apex/clean.py83
-rw-r--r--apex/common/exceptions.py8
-rw-r--r--apex/common/parsers.py25
-rw-r--r--apex/network/jumphost.py236
-rw-r--r--apex/tests/config/bad_ifcfg-br-external8
-rw-r--r--apex/tests/config/bad_nova_output.json23
-rw-r--r--apex/tests/config/ifcfg-br-dummy9
-rw-r--r--apex/tests/config/ifcfg-br-external10
-rw-r--r--apex/tests/config/ifcfg-dummy7
-rw-r--r--apex/tests/test_apex_clean.py69
-rw-r--r--apex/tests/test_apex_common_parsers.py21
-rw-r--r--apex/tests/test_apex_network_jumphost.py276
12 files changed, 706 insertions, 69 deletions
diff --git a/apex/clean.py b/apex/clean.py
index af9e8ce0..81ae1770 100644
--- a/apex/clean.py
+++ b/apex/clean.py
@@ -7,16 +7,21 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-# Clean will eventually be migrated to this file
-
import argparse
+import fileinput
+import libvirt
import logging
import os
import pyipmi
import pyipmi.interfaces
import sys
-from .common import utils
+from apex.common import (
+ constants,
+ utils)
+from apex.network import jumphost
+from apex.common.exceptions import ApexCleanException
+from virtualbmc import manager as vbmc_lib
def clean_nodes(inventory):
@@ -41,11 +46,59 @@ def clean_nodes(inventory):
sys.exit(1)
+def clean_vbmcs():
+ vbmc_manager = vbmc_lib.VirtualBMCManager()
+ vbmcs = vbmc_manager.list()
+ for vbmc in vbmcs:
+ logging.info("Deleting vbmc: {}".format(vbmc['domain_name']))
+ vbmc_manager.delete(vbmc['domain_name'])
+
+
+def clean_vms():
+ logging.info('Destroying all Apex VMs')
+ conn = libvirt.open('qemu:///system')
+ if not conn:
+ raise ApexCleanException('Unable to open libvirt connection')
+ pool = conn.storagePoolLookupByName('default')
+ domains = conn.listAllDomains()
+
+ for domain in domains:
+ vm = domain.name()
+ if vm != 'undercloud' and not vm.startswith('baremetal'):
+ continue
+ logging.info("Cleaning domain: {}".format(vm))
+ if domain.isActive():
+ logging.debug('Destroying domain')
+ domain.destroy()
+ domain.undefine()
+ # delete storage volume
+ try:
+ stgvol = pool.storageVolLookupByName("{}.qcow2".format(vm))
+ except libvirt.libvirtError:
+ logging.warning("Skipping volume cleanup as volume not found for "
+ "vm: {}".format(vm))
+ stgvol = None
+ if stgvol:
+ logging.info('Deleting storage volume')
+ stgvol.wipe(0)
+ stgvol.delete(0)
+ pool.refresh()
+
+
+def clean_ssh_keys(key_file='/root/.ssh/authorized_keys'):
+ logging.info('Removing any stack pub keys from root authorized keys')
+ for line in fileinput.input(key_file, inplace=True):
+ line = line.strip('\n')
+ if 'stack@undercloud' not in line:
+ print(line)
+
+
def main():
clean_parser = argparse.ArgumentParser()
clean_parser.add_argument('-f',
dest='inv_file',
- required=True,
+ required=False,
+ default=None,
help='File which contains inventory')
args = clean_parser.parse_args(sys.argv[1:])
os.makedirs(os.path.dirname('./apex_clean.log'), exist_ok=True)
@@ -58,8 +111,28 @@ def main():
console.setLevel(logging.DEBUG)
console.setFormatter(logging.Formatter(formatter))
logging.getLogger('').addHandler(console)
- clean_nodes(args.inv_file)
+ if args.inv_file:
+ if not os.path.isfile(args.inv_file):
+ logging.error("Inventory file not found: {}".format(args.inv_file))
+ raise FileNotFoundError("Inventory file does not exist")
+ else:
+ logging.info("Shutting down baremetal nodes")
+ clean_nodes(args.inv_file)
+ # Delete all VMs
+ clean_vms()
+ # Delete vbmc
+ clean_vbmcs()
+ # Clean network config
+ for network in constants.ADMIN_NETWORK, constants.EXTERNAL_NETWORK:
+ logging.info("Cleaning Jump Host Network config for network "
+ "{}".format(network))
+ jumphost.detach_interface_from_ovs(network)
+ jumphost.remove_ovs_bridge(network)
+
+ # clean pub keys from root's auth keys
+ clean_ssh_keys()
+ logging.info('Apex clean complete!')
if __name__ == '__main__':
main()
diff --git a/apex/common/exceptions.py b/apex/common/exceptions.py
index c660213f..54d99834 100644
--- a/apex/common/exceptions.py
+++ b/apex/common/exceptions.py
@@ -10,3 +10,11 @@
class ApexDeployException(Exception):
pass
+
+
+class JumpHostNetworkException(Exception):
+ pass
+
+
+class ApexCleanException(Exception):
+ pass
diff --git a/apex/common/parsers.py b/apex/common/parsers.py
index 8744c862..91b8905b 100644
--- a/apex/common/parsers.py
+++ b/apex/common/parsers.py
@@ -71,3 +71,28 @@ def parse_overcloudrc(in_file):
logging.debug("os cred not found in: {}".format(line))
return creds
+
+
+def parse_ifcfg_file(in_file):
+ """
+ Parses ifcfg file information
+ :param in_file:
+ :return: dictionary of ifcfg key value pairs
+ """
+ ifcfg_params = {
+ 'IPADDR': '',
+ 'NETMASK': '',
+ 'GATEWAY': '',
+ 'METRIC': '',
+ 'DNS1': '',
+ 'DNS2': '',
+ 'PREFIX': ''
+ }
+ with open(in_file, 'r') as fh:
+ for line in fh:
+ for param in ifcfg_params.keys():
+ match = re.search("^\s*{}=(.*)$".format(param), line)
+ if match:
+ ifcfg_params[param] = match.group(1)
+ break
+ return ifcfg_params
diff --git a/apex/network/jumphost.py b/apex/network/jumphost.py
index f3f06ad6..2ecb7f4e 100644
--- a/apex/network/jumphost.py
+++ b/apex/network/jumphost.py
@@ -9,11 +9,11 @@
import logging
import os
-import re
import shutil
import subprocess
-from apex.common.exceptions import ApexDeployException
+from apex.common.exceptions import JumpHostNetworkException
+from apex.common import parsers
from apex.network import ip_utils
NET_MAP = {
@@ -24,6 +24,8 @@ NET_MAP = {
'api': 'br-api'
}
+NET_CFG_PATH = '/etc/sysconfig/network-scripts'
+
def configure_bridges(ns):
"""
@@ -68,81 +70,98 @@ def configure_bridges(ns):
except subprocess.CalledProcessError:
logging.error("Unable to configure IP address on "
"bridge {}".format(NET_MAP[network]))
+ raise
-def attach_interface_to_ovs(bridge, interface, network):
+def generate_ifcfg_params(if_file, network):
"""
- Attaches jumphost interface to OVS for baremetal deployments
- :param bridge: bridge to attach to
- :param interface: interface to attach to bridge
- :param network: Apex network type for these interfaces
- :return: None
+ Generates and validates ifcfg parameters required for a network
+ :param if_file: ifcfg file to parse
+ :param network: Apex network
+ :return: dictionary of generated/validated ifcfg params
"""
+ ifcfg_params = parsers.parse_ifcfg_file(if_file)
+ if not ifcfg_params['IPADDR']:
+ logging.error("IPADDR missing in {}".format(if_file))
+ raise JumpHostNetworkException("IPADDR missing in {}".format(if_file))
+ if not (ifcfg_params['NETMASK'] or ifcfg_params['PREFIX']):
+ logging.error("NETMASK/PREFIX missing in {}".format(if_file))
+ raise JumpHostNetworkException("NETMASK/PREFIX missing in {}".format(
+ if_file))
+ if network == 'external' and not ifcfg_params['GATEWAY']:
+ logging.error("GATEWAY is required to be in {} for external "
+ "network".format(if_file))
+ raise JumpHostNetworkException("GATEWAY is required to be in {} for "
+ "external network".format(if_file))
- net_cfg_path = '/etc/sysconfig/network-scripts'
- if_file = os.path.join(net_cfg_path, "ifcfg-{}".format(interface))
- ovs_file = os.path.join(net_cfg_path, "ifcfg-{}".format(bridge))
+ if ifcfg_params['DNS1'] or ifcfg_params['DNS2']:
+ ifcfg_params['PEERDNS'] = 'yes'
+ else:
+ ifcfg_params['PEERDNS'] = 'no'
+ return ifcfg_params
- logging.info("Attaching interface: {} to bridge: {} on network {}".format(
- bridge, interface, network
- ))
+def is_ovs_bridge(bridge):
+ """
+ Finds an OVS bridge
+ :param bridge: OVS bridge to find
+ :return: boolean if OVS bridge exists
+ """
try:
output = subprocess.check_output(['ovs-vsctl', 'show'],
stderr=subprocess.STDOUT)
if bridge not in output.decode('utf-8'):
- logging.debug("Bridge {} not found. Creating...".format(bridge))
- subprocess.check_call(['ovs-vsctl', 'add-br', bridge])
+ logging.debug("Bridge {} not found".format(bridge))
+ return False
else:
logging.debug("Bridge {} found".format(bridge))
+ return True
except subprocess.CalledProcessError:
- logging.error("Unable to validate/create OVS bridge {}".format(bridge))
+ logging.error("Unable to validate OVS bridge {}".format(bridge))
raise
+
+
+def dump_ovs_ports(bridge):
+ """
+ Returns
+ :param bridge: OVS bridge to list ports
+ :return: list of ports
+ """
try:
output = subprocess.check_output(['ovs-vsctl', 'list-ports', bridge],
stderr=subprocess.STDOUT)
- if interface in output.decode('utf-8'):
- logging.debug("Interface already attached to bridge")
- return
- except subprocess.CalledProcessError as e:
- logging.error("Unable to dump ports for bridge: {}".format(bridge))
- logging.error("Error output: {}".format(e.output))
+ except subprocess.CalledProcessError:
+ logging.error("Unable to show ports for {}".format(bridge))
raise
+ return output.decode('utf-8').strip().split('\n')
- if not os.path.isfile(if_file):
- logging.error("Interface ifcfg not found: {}".format(if_file))
- raise FileNotFoundError("Interface file missing: {}".format(if_file))
- ifcfg_params = {
- 'IPADDR': '',
- 'NETMASK': '',
- 'GATEWAY': '',
- 'METRIC': '',
- 'DNS1': '',
- 'DNS2': '',
- 'PREFIX': ''
- }
- with open(if_file, 'r') as fh:
- interface_output = fh.read()
-
- for param in ifcfg_params.keys():
- match = re.search("{}=(.*)\n".format(param), interface_output)
- if match:
- ifcfg_params[param] = match.group(1)
+def attach_interface_to_ovs(bridge, interface, network):
+ """
+ Attaches jumphost interface to OVS for baremetal deployments
+ :param bridge: bridge to attach to
+ :param interface: interface to attach to bridge
+ :param network: Apex network type for these interfaces
+ :return: None
+ """
- if not ifcfg_params['IPADDR']:
- logging.error("IPADDR missing in {}".format(if_file))
- raise ApexDeployException("IPADDR missing in {}".format(if_file))
- if not (ifcfg_params['NETMASK'] or ifcfg_params['PREFIX']):
- logging.error("NETMASK/PREFIX missing in {}".format(if_file))
- raise ApexDeployException("NETMASK/PREFIX missing in {}".format(
- if_file))
- if network == 'external' and not ifcfg_params['GATEWAY']:
- logging.error("GATEWAY is required to be in {} for external "
- "network".format(if_file))
- raise ApexDeployException("GATEWAY is required to be in {} for "
- "external network".format(if_file))
+ if_file = os.path.join(NET_CFG_PATH, "ifcfg-{}".format(interface))
+ ovs_file = os.path.join(NET_CFG_PATH, "ifcfg-{}".format(bridge))
+
+ logging.info("Attaching interface: {} to bridge: {} on network {}".format(
+ bridge, interface, network
+ ))
+ if not is_ovs_bridge(bridge):
+ subprocess.check_call(['ovs-vsctl', 'add-br', bridge])
+ elif interface in dump_ovs_ports(bridge):
+ logging.debug("Interface already attached to bridge")
+ return
+
+ if not os.path.isfile(if_file):
+ logging.error("Interface ifcfg not found: {}".format(if_file))
+ raise FileNotFoundError("Interface file missing: {}".format(if_file))
+ ifcfg_params = generate_ifcfg_params(if_file, network)
shutil.move(if_file, "{}.orig".format(if_file))
if_content = """DEVICE={}
DEVICETYPE=ovs
@@ -160,13 +179,9 @@ BOOTPROTO=static
ONBOOT=yes
TYPE=OVSBridge
PROMISC=yes""".format(bridge)
- peer_dns = 'no'
for param, value in ifcfg_params.items():
if value:
bridge_content += "\n{}={}".format(param, value)
- if param == 'DNS1' or param == 'DNS2':
- peer_dns = 'yes'
- bridge_content += "\n{}={}".format('PEERDNS', peer_dns)
logging.debug("New interface file content:\n{}".format(if_content))
logging.debug("New bridge file content:\n{}".format(bridge_content))
@@ -181,3 +196,108 @@ PROMISC=yes""".format(bridge)
except subprocess.CalledProcessError:
logging.error("Failed to restart Linux networking")
raise
+
+
+def detach_interface_from_ovs(network):
+ """
+ Detach interface from OVS for baremetal deployments
+ :param network: Apex network to detach single interface from
+ :return: None
+ """
+
+ bridge = NET_MAP[network]
+ logging.debug("Detaching interfaces from bridge on network: {}".format(
+ network))
+ # ensure bridge exists
+ if not is_ovs_bridge(bridge):
+ return
+
+ # check if real port is on bridge
+ for interface in dump_ovs_ports(bridge):
+ if interface and not interface.startswith('vnet'):
+ logging.debug("Interface found: {}".format(interface))
+ real_interface = interface
+ break
+ else:
+ logging.info("No jumphost interface exists on bridge {}".format(
+ bridge))
+ return
+
+ # check if original backup ifcfg file exists or create
+ orig_ifcfg_file = os.path.join(NET_CFG_PATH,
+ "ifcfg-{}.orig".format(real_interface))
+ ifcfg_file = orig_ifcfg_file[:-len('.orig')]
+ if os.path.isfile(orig_ifcfg_file):
+ logging.debug("Original interface file found: "
+ "{}".format(orig_ifcfg_file))
+ shutil.move(orig_ifcfg_file, ifcfg_file)
+ else:
+ logging.info("No original ifcfg file found...will attempt to use "
+ "bridge icfg file and re-create")
+ bridge_ifcfg_file = os.path.join(NET_CFG_PATH,
+ "ifcfg-{}".format(bridge))
+ if os.path.isfile(bridge_ifcfg_file):
+ ifcfg_params = generate_ifcfg_params(bridge_ifcfg_file, network)
+ if_content = """DEVICE={}
+BOOTPROTO=static
+ONBOOT=yes
+TYPE=Ethernet
+NM_CONTROLLED=no""".format(real_interface)
+ for param, value in ifcfg_params.items():
+ if value:
+ if_content += "\n{}={}".format(param, value)
+ logging.debug("Interface file content:\n{}".format(if_content))
+ # write original backup
+ with open(orig_ifcfg_file, 'w') as fh:
+ fh.write(if_content)
+ logging.debug("Original interface file created: "
+ "{}".format(orig_ifcfg_file))
+ else:
+ logging.error("Unable to find original interface config file: {} "
+ "or bridge config file:{}".format(orig_ifcfg_file,
+ bridge_ifcfg_file))
+ raise FileNotFoundError("Unable to locate bridge or original "
+ "interface ifcfg file")
+
+ # move original file back and rewrite bridge ifcfg
+ shutil.move(orig_ifcfg_file, ifcfg_file)
+ bridge_content = """DEVICE={}
+DEVICETYPE=ovs
+BOOTPROTO=static
+ONBOOT=yes
+TYPE=OVSBridge
+PROMISC=yes""".format(bridge)
+ with open(bridge_ifcfg_file, 'w') as fh:
+ fh.write(bridge_content)
+ # restart linux networking
+ logging.info("Restarting Linux networking")
+ try:
+ subprocess.check_call(['systemctl', 'restart', 'network'])
+ except subprocess.CalledProcessError:
+ logging.error("Failed to restart Linux networking")
+ raise
+
+
+def remove_ovs_bridge(network):
+ """
+ Unconfigure and remove an OVS bridge
+ :param network: Apex network to remove OVS bridge for
+ :return:
+ """
+ bridge = NET_MAP[network]
+ if is_ovs_bridge(bridge):
+ logging.info("Removing bridge: {}".format(bridge))
+ try:
+ subprocess.check_call(['ovs-vsctl', 'del-br', bridge])
+ except subprocess.CalledProcessError:
+ logging.error('Unable to destroy OVS bridge')
+ raise
+
+ logging.debug('Bridge destroyed')
+ bridge_ifcfg_file = os.path.join(NET_CFG_PATH,
+ "ifcfg-{}".format(bridge))
+ if os.path.isfile(bridge_ifcfg_file):
+ os.remove(bridge_ifcfg_file)
+ logging.debug("Bridge ifcfg file removed: {}".format)
+ else:
+ logging.debug('Bridge ifcfg file not found')
diff --git a/apex/tests/config/bad_ifcfg-br-external b/apex/tests/config/bad_ifcfg-br-external
new file mode 100644
index 00000000..85b81959
--- /dev/null
+++ b/apex/tests/config/bad_ifcfg-br-external
@@ -0,0 +1,8 @@
+DEVICE=br-external
+DEVICETYPE=ovs
+BOOTPROTO=static
+ONBOOT=yes
+TYPE=OVSBridge
+PROMISC=yes
+IPADDR=172.30.9.66
+NETMASK=255.255.255.0
diff --git a/apex/tests/config/bad_nova_output.json b/apex/tests/config/bad_nova_output.json
new file mode 100644
index 00000000..137750e5
--- /dev/null
+++ b/apex/tests/config/bad_nova_output.json
@@ -0,0 +1,23 @@
+[
+ {
+ "Status": "ACTIVE",
+ "Networks": "",
+ "ID": "a5ff8aeb-5fd0-467f-9d89-791dfbc6267b",
+ "Image Name": "overcloud-full",
+ "Name": "test3"
+ },
+ {
+ "Status": "ACTIVE",
+ "Networks": "",
+ "ID": "c8be26ae-6bef-4841-bb03-c7f336cfd785",
+ "Image Name": "overcloud-full",
+ "Name": "test2"
+ },
+ {
+ "Status": "ACTIVE",
+ "Networks": "",
+ "ID": "105d1c61-78d3-498f-9191-6b21823b8544",
+ "Image Name": "overcloud-full",
+ "Name": "test1"
+ }
+]
diff --git a/apex/tests/config/ifcfg-br-dummy b/apex/tests/config/ifcfg-br-dummy
new file mode 100644
index 00000000..117ca726
--- /dev/null
+++ b/apex/tests/config/ifcfg-br-dummy
@@ -0,0 +1,9 @@
+DEVICE=br-dummy
+DEVICETYPE=ovs
+BOOTPROTO=static
+ONBOOT=yes
+TYPE=OVSBridge
+PROMISC=yes
+IPADDR=152.30.9.11
+NETMASK=255.255.255.0
+PEERDNS=no \ No newline at end of file
diff --git a/apex/tests/config/ifcfg-br-external b/apex/tests/config/ifcfg-br-external
new file mode 100644
index 00000000..9717d6e3
--- /dev/null
+++ b/apex/tests/config/ifcfg-br-external
@@ -0,0 +1,10 @@
+DEVICE=br-external
+DEVICETYPE=ovs
+BOOTPROTO=static
+ONBOOT=yes
+TYPE=OVSBridge
+PROMISC=yes
+IPADDR=172.30.9.66
+NETMASK=255.255.255.0
+GATEWAY=172.30.9.1
+#DNS1=1.1.1.1
diff --git a/apex/tests/config/ifcfg-dummy b/apex/tests/config/ifcfg-dummy
new file mode 100644
index 00000000..f9ca21d4
--- /dev/null
+++ b/apex/tests/config/ifcfg-dummy
@@ -0,0 +1,7 @@
+DEVICE=enpfakes0
+TYPE=Ethernet
+ONBOOT=yes
+BOOTPROTO=static
+NM_CONTROLLED=no
+IPADDR=152.30.9.11
+NETMASK=255.255.255.0
diff --git a/apex/tests/test_apex_clean.py b/apex/tests/test_apex_clean.py
index 7b7df512..b6b9d428 100644
--- a/apex/tests/test_apex_clean.py
+++ b/apex/tests/test_apex_clean.py
@@ -8,12 +8,48 @@
##############################################################################
import mock
+import os
import pyipmi
import pyipmi.chassis
from mock import patch
-from nose import tools
+from nose.tools import (
+ assert_raises,
+ assert_equal
+)
from apex import clean_nodes
+from apex import clean
+from apex.tests import constants as con
+
+
+class dummy_domain:
+
+ def isActive(self):
+ return True
+
+ def destroy(self):
+ pass
+
+ def undefine(self):
+ pass
+
+
+class dummy_vol:
+
+ def wipe(self, *args):
+ pass
+
+ def delete(self, *args):
+ pass
+
+
+class dummy_pool:
+
+ def storageVolLookupByName(self, *args, **kwargs):
+ return dummy_vol()
+
+ def refresh(self):
+ pass
class TestClean:
@@ -31,11 +67,36 @@ class TestClean:
def teardown(self):
"""This method is run once after _each_ test method is executed"""
- def test_clean(self):
+ def test_clean_nodes(self):
with mock.patch.object(pyipmi.Session, 'establish') as mock_method:
with patch.object(pyipmi.chassis.Chassis,
'chassis_control_power_down') as mock_method2:
clean_nodes('apex/tests/config/inventory.yaml')
- tools.assert_equal(mock_method.call_count, 5)
- tools.assert_equal(mock_method2.call_count, 5)
+ assert_equal(mock_method.call_count, 5)
+ assert_equal(mock_method2.call_count, 5)
+
+ @patch('virtualbmc.manager.VirtualBMCManager.list',
+ return_value=[{'domain_name': 'dummy1'}, {'domain_name': 'dummy2'}])
+ @patch('virtualbmc.manager.VirtualBMCManager.delete')
+ def test_vmbc_clean(self, vbmc_del_func, vbmc_list_func):
+ assert clean.clean_vbmcs() is None
+
+ def test_clean_ssh_keys(self):
+ ssh_file = os.path.join(con.TEST_DUMMY_CONFIG, 'authorized_dummy')
+ with open(ssh_file, 'w') as fh:
+ fh.write('ssh-rsa 2LwlofGD8rNUFAlafY2/oUsKOf1mQ1 stack@undercloud')
+ assert clean.clean_ssh_keys(ssh_file) is None
+ with open(ssh_file, 'r') as fh:
+ output = fh.read()
+ assert 'stack@undercloud' not in output
+ if os.path.isfile(ssh_file):
+ os.remove(ssh_file)
+
+ @patch('libvirt.open')
+ def test_clean_vms(self, mock_libvirt):
+ ml = mock_libvirt.return_value
+ ml.storagePoolLookupByName.return_value = dummy_pool()
+ ml.listDefinedDomains.return_value = ['undercloud']
+ ml.lookupByName.return_value = dummy_domain()
+ assert clean.clean_vms() is None
diff --git a/apex/tests/test_apex_common_parsers.py b/apex/tests/test_apex_common_parsers.py
index bed2a8c5..d272a749 100644
--- a/apex/tests/test_apex_common_parsers.py
+++ b/apex/tests/test_apex_common_parsers.py
@@ -11,9 +11,11 @@ import os
from apex.tests import constants as con
from apex.common import parsers as apex_parsers
+from apex.common.exceptions import ApexDeployException
from nose.tools import (
assert_is_instance,
- assert_dict_equal
+ assert_dict_equal,
+ assert_raises
)
@@ -41,9 +43,13 @@ class TestCommonParsers:
'overcloud-novacompute-0': '192.30.9.10',
'overcloud-novacompute-1': '192.30.9.9'
}
- print(output)
assert_dict_equal(output, nodes)
+ def test_negative_parse_nova_output(self):
+ assert_raises(ApexDeployException, apex_parsers.parse_nova_output,
+ os.path.join(con.TEST_DUMMY_CONFIG,
+ 'bad_nova_output.json'))
+
def test_parse_overcloudrc(self):
output = apex_parsers.parse_overcloudrc(
os.path.join(con.TEST_DUMMY_CONFIG, 'test_overcloudrc'))
@@ -52,3 +58,14 @@ class TestCommonParsers:
assert output['OS_AUTH_TYPE'] == 'password'
assert 'OS_PASSWORD' in output.keys()
assert output['OS_PASSWORD'] == 'Wd8ruyf6qG8cmcms6dq2HM93f'
+
+ def test_parse_ifcfg(self):
+ output = apex_parsers.parse_ifcfg_file(
+ os.path.join(con.TEST_DUMMY_CONFIG, 'ifcfg-br-external'))
+ assert_is_instance(output, dict)
+ assert 'IPADDR' in output.keys()
+ assert output['IPADDR'] == '172.30.9.66'
+ assert 'NETMASK' in output.keys()
+ assert output['NETMASK'] == '255.255.255.0'
+ assert 'DNS1' in output.keys()
+ assert not output['DNS1']
diff --git a/apex/tests/test_apex_network_jumphost.py b/apex/tests/test_apex_network_jumphost.py
new file mode 100644
index 00000000..a23f1c56
--- /dev/null
+++ b/apex/tests/test_apex_network_jumphost.py
@@ -0,0 +1,276 @@
+##############################################################################
+# Copyright (c) 2016 Dan Radez (Red Hat)
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import os
+import shutil
+import subprocess
+
+from apex import NetworkSettings
+from apex.tests import constants as con
+from apex.common import constants as apex_constants
+from apex.network import jumphost
+from apex.common.exceptions import JumpHostNetworkException
+from ipaddress import IPv4Interface
+from mock import patch
+from nose.tools import (
+ assert_is_instance,
+ assert_dict_equal,
+ assert_raises,
+ assert_true,
+ assert_false
+)
+
+
+def bridge_show_output(*args, **kwargs):
+ return b"""
+ b6f1b54a-b8ba-4e86-9c5b-733ab71b5712
+ Bridge br-admin
+ Port br-admin
+ Interface br-admin
+ type: internal
+ ovs_version: "2.5.0"
+"""
+
+
+def bridge_port_list(*args, **kwargs):
+ return b"""
+enp6s0
+vnet1
+"""
+
+
+def subprocess_exception(*args, **kwargs):
+ raise subprocess.CalledProcessError(returncode=2, cmd='dummy')
+
+
+class TestNetworkJumpHost:
+ @classmethod
+ def setup_class(cls):
+ """This method is run once for each class before any tests are run"""
+
+ @classmethod
+ def teardown_class(cls):
+ """This method is run once for each class _after_ all tests are run"""
+
+ def setup(self):
+ """This method is run once before _each_ test method is executed"""
+
+ def teardown(self):
+ """This method is run once after _each_ test method is executed"""
+
+ @patch('subprocess.check_output', side_effect=bridge_show_output)
+ def test_is_ovs_bridge(self, bridge_output_function):
+ assert_true(jumphost.is_ovs_bridge('br-admin'))
+ assert_false(jumphost.is_ovs_bridge('br-blah'))
+
+ @patch('subprocess.check_output', side_effect=bridge_port_list)
+ def test_dump_ovs_ports(self, bridge_function):
+ output = jumphost.dump_ovs_ports('br-admin')
+ assert_is_instance(output, list)
+ assert 'enp6s0' in output
+
+ def test_generate_ifcfg_params(self):
+ output = jumphost.generate_ifcfg_params(
+ os.path.join(con.TEST_DUMMY_CONFIG, 'ifcfg-br-external'),
+ apex_constants.EXTERNAL_NETWORK)
+ assert_is_instance(output, dict)
+ assert output['IPADDR'] == '172.30.9.66'
+ assert output['PEERDNS'] == 'no'
+
+ def test_negative_generate_ifcfg_params(self):
+ assert_raises(JumpHostNetworkException, jumphost.generate_ifcfg_params,
+ os.path.join(con.TEST_DUMMY_CONFIG,
+ 'bad_ifcfg-br-external'),
+ apex_constants.EXTERNAL_NETWORK)
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.ip_utils.get_interface', return_value=IPv4Interface(
+ '10.10.10.2'))
+ def test_configure_bridges_ip_exists(self, interface_function,
+ subprocess_func):
+ ns = NetworkSettings(os.path.join(con.TEST_CONFIG_DIR,
+ 'network', 'network_settings.yaml'))
+ assert jumphost.configure_bridges(ns) is None
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.ip_utils.get_interface', return_value=None)
+ def test_configure_bridges_no_ip(self, interface_function,
+ subprocess_func):
+ ns = NetworkSettings(os.path.join(con.TEST_CONFIG_DIR,
+ 'network', 'network_settings.yaml'))
+ assert jumphost.configure_bridges(ns) is None
+
+ @patch('subprocess.check_call', side_effect=subprocess_exception)
+ @patch('apex.network.ip_utils.get_interface', return_value=None)
+ def test_negative_configure_bridges(self, interface_function,
+ subprocess_func):
+ ns = NetworkSettings(os.path.join(con.TEST_CONFIG_DIR,
+ 'network', 'network_settings.yaml'))
+ assert_raises(subprocess.CalledProcessError,
+ jumphost.configure_bridges, ns)
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=[])
+ def test_attach_interface(self, dump_ports_func, is_bridge_func,
+ subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-enpfakes0'))
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-br-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ output = jumphost.attach_interface_to_ovs('br-admin', 'enpfakes0',
+ 'admin')
+ assert output is None
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-enpfakes0'))
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-enpfakes0.orig'))
+
+ for ifcfg in ('ifcfg-enpfakes0', 'ifcfg-enpfakes0.orig',
+ 'ifcfg-br-admin'):
+ ifcfg_path = os.path.join(ifcfg_dir, ifcfg)
+ if os.path.isfile(ifcfg_path):
+ os.remove(ifcfg_path)
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=['dummy_int'])
+ def test_already_attached_interface(self, dump_ports_func, is_bridge_func,
+ subprocess_func):
+ output = jumphost.attach_interface_to_ovs('br-dummy', 'dummy_int',
+ 'admin')
+ assert output is None
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=[])
+ def test_negative_attach_interface(self, dump_ports_func, is_bridge_func,
+ subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ assert_raises(FileNotFoundError, jumphost.attach_interface_to_ovs,
+ 'br-dummy', 'dummy_int', 'admin')
+
+ @patch('subprocess.check_call', side_effect=subprocess_exception)
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=[])
+ def test_negative_attach_interface_process_error(
+ self, dump_ports_func, is_bridge_func, subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-enpfakes0'))
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-br-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ assert_raises(subprocess.CalledProcessError,
+ jumphost.attach_interface_to_ovs,
+ 'br-admin', 'enpfakes0', 'admin')
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-enpfakes0'))
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-enpfakes0.orig'))
+
+ for ifcfg in ('ifcfg-enpfakes0', 'ifcfg-enpfakes0.orig',
+ 'ifcfg-br-admin'):
+ ifcfg_path = os.path.join(ifcfg_dir, ifcfg)
+ if os.path.isfile(ifcfg_path):
+ os.remove(ifcfg_path)
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=['enpfakes0'])
+ def test_detach_interface(self, dump_ports_func, is_bridge_func,
+ subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-br-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ output = jumphost.detach_interface_from_ovs('admin')
+ assert output is None
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-enpfakes0'))
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+
+ for ifcfg in ('ifcfg-enpfakes0', 'ifcfg-enpfakes0.orig',
+ 'ifcfg-br-admin'):
+ ifcfg_path = os.path.join(ifcfg_dir, ifcfg)
+ if os.path.isfile(ifcfg_path):
+ os.remove(ifcfg_path)
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=False)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=[])
+ def test_detach_interface_no_bridge(self, dump_ports_func,
+ is_bridge_func, subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ output = jumphost.detach_interface_from_ovs('admin')
+ assert output is None
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=[])
+ def test_detach_interface_no_int_to_remove(self, dump_ports_func,
+ is_bridge_func,
+ subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ output = jumphost.detach_interface_from_ovs('admin')
+ assert output is None
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=['enpfakes0'])
+ def test_negative_detach_interface(self, dump_ports_func, is_bridge_func,
+ subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ assert_raises(FileNotFoundError, jumphost.detach_interface_from_ovs,
+ 'admin')
+
+ @patch('subprocess.check_call', side_effect=subprocess_exception)
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ @patch('apex.network.jumphost.dump_ovs_ports', return_value=['enpfakes0'])
+ def test_negative_detach_interface_process_error(
+ self, dump_ports_func, is_bridge_func, subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-br-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ assert_raises(subprocess.CalledProcessError,
+ jumphost.detach_interface_from_ovs, 'admin')
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-enpfakes0'))
+ assert os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+
+ for ifcfg in ('ifcfg-enpfakes0', 'ifcfg-enpfakes0.orig',
+ 'ifcfg-br-admin'):
+ ifcfg_path = os.path.join(ifcfg_dir, ifcfg)
+ if os.path.isfile(ifcfg_path):
+ os.remove(ifcfg_path)
+
+ @patch('subprocess.check_call')
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ def test_remove_ovs_bridge(self, is_bridge_func, subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ shutil.copyfile(os.path.join(ifcfg_dir, 'ifcfg-br-dummy'),
+ os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+ assert jumphost.remove_ovs_bridge(apex_constants.ADMIN_NETWORK) is None
+ assert not os.path.isfile(os.path.join(ifcfg_dir, 'ifcfg-br-admin'))
+
+ # test without file
+ assert jumphost.remove_ovs_bridge(apex_constants.ADMIN_NETWORK) is None
+
+ @patch('subprocess.check_call', side_effect=subprocess_exception)
+ @patch('apex.network.jumphost.is_ovs_bridge', return_value=True)
+ def test_negative_remove_ovs_bridge(self, is_bridge_func, subprocess_func):
+ ifcfg_dir = con.TEST_DUMMY_CONFIG
+ jumphost.NET_CFG_PATH = ifcfg_dir
+ assert_raises(subprocess.CalledProcessError,
+ jumphost.remove_ovs_bridge,
+ apex_constants.ADMIN_NETWORK)