Migrates clean to python 39/41439/10
authorTim Rozet <trozet@redhat.com>
Fri, 8 Sep 2017 20:57:36 +0000 (16:57 -0400)
committerTim Rozet <trozet@redhat.com>
Wed, 13 Sep 2017 14:13:06 +0000 (10:13 -0400)
ci/clean.sh will be removed in a future patch after releng is updated to
use python.

JIRA: APEX-509
JIRA: APEX-319

Change-Id: If890db2fc5a31833ad28ec6f04589e25457bd380
Signed-off-by: Tim Rozet <trozet@redhat.com>
15 files changed:
apex/clean.py
apex/common/exceptions.py
apex/common/parsers.py
apex/network/jumphost.py
apex/tests/config/bad_ifcfg-br-external [new file with mode: 0644]
apex/tests/config/bad_nova_output.json [new file with mode: 0644]
apex/tests/config/ifcfg-br-dummy [new file with mode: 0644]
apex/tests/config/ifcfg-br-external [new file with mode: 0644]
apex/tests/config/ifcfg-dummy [new file with mode: 0644]
apex/tests/test_apex_clean.py
apex/tests/test_apex_common_parsers.py
apex/tests/test_apex_network_jumphost.py [new file with mode: 0644]
build/rpm_specs/opnfv-apex-common.spec
ci/clean.sh
setup.cfg

index af9e8ce..81ae177 100644 (file)
@@ -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()
index c660213..54d9983 100644 (file)
 
 class ApexDeployException(Exception):
     pass
+
+
+class JumpHostNetworkException(Exception):
+    pass
+
+
+class ApexCleanException(Exception):
+    pass
index 8744c86..91b8905 100644 (file)
@@ -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
index f3f06ad..2ecb7f4 100644 (file)
@@ -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 (file)
index 0000000..85b8195
--- /dev/null
@@ -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 (file)
index 0000000..137750e
--- /dev/null
@@ -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 (file)
index 0000000..117ca72
--- /dev/null
@@ -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 (file)
index 0000000..9717d6e
--- /dev/null
@@ -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 (file)
index 0000000..f9ca21d
--- /dev/null
@@ -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
index 7b7df51..b6b9d42 100644 (file)
@@ -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
index bed2a8c..d272a74 100644 (file)
@@ -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 (file)
index 0000000..a23f1c5
--- /dev/null
@@ -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)
index 37e3214..c2e2f14 100644 (file)
@@ -35,7 +35,6 @@ rst2html docs/release/release-notes/release-notes.rst docs/release/release-notes
 %install
 mkdir -p %{buildroot}%{_bindir}/
 %py3_install
-install ci/clean.sh %{buildroot}%{_bindir}/opnfv-clean
 install ci/util.sh %{buildroot}%{_bindir}/opnfv-util
 
 mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d/
@@ -113,6 +112,8 @@ install config/inventory/pod_example_settings.yaml %{buildroot}%{_docdir}/opnfv/
 %doc %{_docdir}/opnfv/inventory.yaml.example
 
 %changelog
+* Fri Sep 08 2017 Tim Rozet <trozet@redhat.com> - 5.0-6
+- Updates clean to use python
 * Wed Aug 23 2017 Tim Rozet <trozet@redhat.com> - 5.0-5
 - Updated requirements
 * Mon Aug 14 2017 Tim Rozet <trozet@redhat.com> - 5.0-4
index ef81041..c2b987c 100755 (executable)
 #Clean script to uninstall provisioning server for Apex
 #author: Dan Radez (dradez@redhat.com)
 #author: Tim Rozet (trozet@redhat.com)
-
-reset=$(tput sgr0 || echo "")
-blue=$(tput setaf 4 || echo "")
-red=$(tput setaf 1 || echo "")
-green=$(tput setaf 2 || echo "")
-
-vm_index=4
-ovs_bridges="br-admin br-tenant br-external br-storage"
-ovs_bridges+=" br-private br-public" # Legacy names, remove in E river
-
-#OPNFV_NETWORK_TYPES=$(python3 -c 'from apex.common.constants import OPNFV_NETWORK_TYPES; print(" ".join(OPNFV_NETWORK_TYPES))')
-OPNFV_NETWORK_TYPES+=" admin tenant external storage api"
-OPNFV_NETWORK_TYPES+=" admin_network private_network public_network storage_network api_network" # Legecy names, remove in E river
-
-##detach interface from OVS and set the network config correctly
-##params: bridge to detach from
-##assumes only 1 real interface attached to OVS
-function detach_interface_from_ovs {
-  local bridge
-  local port_output ports_no_orig
-  local net_path
-  local if_ip if_mask if_gw if_prefix
-  local if_metric if_dns1 if_dns2
-
-  net_path=/etc/sysconfig/network-scripts/
-  if [[ -z "$1" ]]; then
-    return 1
-  else
-    bridge=$1
-  fi
-
-  # if no interfaces attached then return
-  if ! ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*"; then
-    return 0
-  fi
-
-  # look for .orig ifcfg files  to use
-  port_output=$(ovs-vsctl list-ports ${bridge} | grep -Ev "vnet[0-9]*")
-  while read -r line; do
-    if [ -z "$line" ]; then
-      continue
-    elif [ -e ${net_path}/ifcfg-${line}.orig ]; then
-      mv -f ${net_path}/ifcfg-${line}.orig ${net_path}/ifcfg-${line}
-    elif [ -e ${net_path}/ifcfg-${bridge} ]; then
-      if_ip=$(sed -n 's/^IPADDR=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge})
-      if_mask=$(sed -n 's/^NETMASK=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge})
-      if_gw=$(sed -n 's/^GATEWAY=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge})
-      if_metric=$(sed -n 's/^METRIC=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge})
-      if_dns1=$(sed -n 's/^DNS1=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge})
-      if_dns2=$(sed -n 's/^DNS2=\(.*\)$/\1/p' ${net_path}/ifcfg-${bridge})
-
-      if [ -z "$if_mask" ]; then
-        if_prefix=$(sed -n 's/^PREFIX=[^0-9]*\([0-9][0-9]*\)[^0-9]*$/\1/p' ${net_path}/ifcfg-${bridge})
-        if_mask=$(prefix2mask ${if_prefix})
-      fi
-
-      if [[ -z "$if_ip" || -z "$if_mask" ]]; then
-        echo "ERROR: IPADDR or PREFIX/NETMASK missing for ${bridge} and no .orig file for interface ${line}"
-        return 1
-      fi
-
-      # create if cfg
-      echo "DEVICE=${line}
-IPADDR=${if_ip}
-NETMASK=${if_mask}
-BOOTPROTO=static
-ONBOOT=yes
-TYPE=Ethernet
-NM_CONTROLLED=no
-PEERDNS=no" > ${net_path}/ifcfg-${line}
-
-      if [ -n "$if_gw" ]; then
-        echo "GATEWAY=${if_gw}" >> ${net_path}/ifcfg-${line}
-      fi
-
-      if [ -n "$if_metric" ]; then
-        echo "METRIC=${if_metric}" >> ${net_path}/ifcfg-${line}
-      fi
-
-      if [[ -n "$if_dns1" || -n "$if_dns2" ]]; then
-        sed -i '/PEERDNS/c\PEERDNS=yes' ${net_path}/ifcfg-${line}
-
-        if [ -n "$if_dns1" ]; then
-          echo "DNS1=${if_dns1}" >> ${net_path}/ifcfg-${line}
-        fi
-
-        if [ -n "$if_dns2" ]; then
-          echo "DNS2=${if_dns2}" >> ${net_path}/ifcfg-${line}
-        fi
-      fi
-      break
-    else
-      echo "ERROR: Real interface ${line} attached to bridge, but no interface or ${bridge} ifcfg file exists"
-      return 1
-    fi
-
-  done <<< "$port_output"
-
-  # modify the bridge ifcfg file
-  # to remove IP params
-  sudo sed -i 's/IPADDR=.*//' ${net_path}/ifcfg-${bridge}
-  sudo sed -i 's/NETMASK=.*//' ${net_path}/ifcfg-${bridge}
-  sudo sed -i 's/GATEWAY=.*//' ${net_path}/ifcfg-${bridge}
-  sudo sed -i 's/DNS1=.*//' ${net_path}/ifcfg-${bridge}
-  sudo sed -i 's/DNS2=.*//' ${net_path}/ifcfg-${bridge}
-  sudo sed -i 's/METRIC=.*//' ${net_path}/ifcfg-${bridge}
-  sudo sed -i 's/PEERDNS=.*//' ${net_path}/ifcfg-${bridge}
-
-  sudo systemctl restart network
-}
-
-display_usage() {
-  echo -e "Usage:\n$0 [arguments] \n"
-  echo -e "   -i|--inventory : Full path to inventory yaml file. Required only for baremetal node clean"
-}
-
-##translates the command line parameters into variables
-##params: $@ the entire command line is passed
-##usage: parse_cmd_line() "$@"
-parse_cmdline() {
-  echo -e "\n\n${blue}This script is used to clean an Apex environment${reset}\n\n"
-  echo "Use -h to display help"
-  sleep 2
-
-  while [ "${1:0:1}" = "-" ]
-  do
-    case "$1" in
-        -h|--help)
-                display_usage
-                exit 0
-            ;;
-        -i|--inventory)
-                INVENTORY_FILE=$2
-                shift 2
-            ;;
-        *)
-                display_usage
-                exit 1
-            ;;
-    esac
-  done
-
-  if [[ ! -z "$INVENTORY_FILE" && ! -f "$INVENTORY_FILE" ]]; then
-    echo -e "{$red}ERROR: Inventory File: ${INVENTORY_FILE} does not exist! Exiting...${reset}"
-    exit 1
-  fi
-}
-
-parse_cmdline "$@"
-
-if [ -n "$INVENTORY_FILE" ]; then
-  echo -e "${blue}INFO: Parsing inventory file...${reset}"
-  # hack for now (until we switch fully over to clean.py) to tell if
-  # we should install apex from python or if rpm is being used
-  if ! rpm -q python34-opnfv-apex > /dev/null; then
-    pushd ../ && python3 setup.py install > /dev/null
-    popd
-  fi
-  if ! python3 -m apex.clean -f ${INVENTORY_FILE}; then
-    echo -e "${red}WARN: Unable to shutdown all nodes! Please check /var/log/apex.log${reset}"
-  else
-    echo -e "${blue}INFO: Node shutdown complete...${reset}"
-  fi
-fi
-
-# Clean off instack/undercloud VM
-for vm in instack undercloud; do
-  virsh destroy $vm 2> /dev/null | xargs echo -n
-  virsh undefine --nvram $vm 2> /dev/null | xargs echo -n
-  /usr/bin/touch /var/lib/libvirt/images/${vm}.qcow2
-  virsh vol-delete ${vm}.qcow2 --pool default 2> /dev/null | xargs echo -n
-  rm -f /var/lib/libvirt/images/${vm}.qcow2 2> /dev/null
-done
-
-# Clean off baremetal VMs in case they exist
-for i in $(seq 0 $vm_index); do
-  virsh destroy baremetal$i 2> /dev/null | xargs echo -n
-  virsh undefine baremetal$i 2> /dev/null | xargs echo -n
-  /usr/bin/touch /var/lib/libvirt/images/baremetal${i}.qcow2
-  virsh vol-delete baremetal${i}.qcow2 --pool default 2> /dev/null | xargs echo -n
-  rm -f /var/lib/libvirt/images/baremetal${i}.qcow2 2> /dev/null
-  if [ -e /root/.vbmc/baremetal$i ]; then vbmc delete baremetal$i; fi
-done
-
-for network in ${OPNFV_NETWORK_TYPES}; do
-  virsh net-destroy ${network} 2> /dev/null
-  virsh net-undefine ${network} 2> /dev/null
-done
-
-# Clean off created bridges
-for bridge in ${ovs_bridges}; do
-  if detach_interface_from_ovs ${bridge} 2> /dev/null; then
-    ovs-vsctl del-br ${bridge} 2> /dev/null
-    rm -f /etc/sysconfig/network-scripts/ifcfg-${bridge}
-  fi
-done
-
-# clean pub keys from root's auth keys
-sed -i '/stack@undercloud.localdomain/d' /root/.ssh/authorized_keys
-
-
-# force storage cleanup
-virsh pool-refresh default
-
-# remove temporary files
-rm -f /tmp/network-environment.yaml
-
-echo "Cleanup Completed"
+set -e
+yum -y install python34 python34-devel libvirt-devel python34-pip python-tox ansible
+mkdir -p ~/tmp
+mv -f .build ~/tmp/
+sudo pip3 install --upgrade --force-reinstall .
+mv -f ~/tmp/.build .
+opnfv-clean $@
index ee3105a..9c181f5 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -23,6 +23,7 @@ setup-hooks =
 [entry_points]
 console_scripts =
     opnfv-deploy = apex.deploy:main
+    opnfv-clean = apex.clean:main
 
 [files]
 packages =