NFVBENCH-161 NFVbench --force-cleanup deletes more ports than needed
[nfvbench.git] / nfvbench / cleanup.py
index 819514a..6cb2713 100644 (file)
@@ -22,8 +22,9 @@ from novaclient.client import Client
 from novaclient.exceptions import NotFound
 from tabulate import tabulate
 
-import credentials as credentials
-from log import LOG
+from . import credentials
+from .log import LOG
+
 
 class ComputeCleaner(object):
     """A cleaner for compute resources."""
@@ -45,30 +46,42 @@ class ComputeCleaner(object):
     def get_resource_list(self):
         return [["Instance", server.name, server.id] for server in self.servers]
 
-    def clean(self):
-        if self.servers:
-            for server in self.servers:
-                try:
-                    LOG.info('Deleting instance %s...', server.name)
-                    self.nova_client.servers.delete(server.id)
-                except Exception:
-                    LOG.exception("Instance %s deletion failed", server.name)
-            LOG.info('    Waiting for %d instances to be fully deleted...', len(self.servers))
-            retry_count = 15 + len(self.servers) * 5
-            while True:
-                retry_count -= 1
-                self.servers = [server for server in self.servers if self.instance_exists(server)]
-                if not self.servers:
-                    break
+    def get_cleaner_code(self):
+        return "instances"
 
-                if retry_count:
-                    LOG.info('    %d yet to be deleted by Nova, retries left=%d...',
-                             len(self.servers), retry_count)
-                    time.sleep(2)
-                else:
-                    LOG.warning('    instance deletion verification time-out: %d still not deleted',
-                                len(self.servers))
-                    break
+    def clean_needed(self, clean_options):
+        if clean_options is None:
+            return True
+        code = self.get_cleaner_code()
+        return code[0] in clean_options
+
+    def clean(self, clean_options):
+        if self.clean_needed(clean_options):
+            if self.servers:
+                for server in self.servers:
+                    try:
+                        LOG.info('Deleting instance %s...', server.name)
+                        self.nova_client.servers.delete(server.id)
+                    except Exception:
+                        LOG.exception("Instance %s deletion failed", server.name)
+                LOG.info('    Waiting for %d instances to be fully deleted...', len(self.servers))
+                retry_count = 15 + len(self.servers) * 5
+                while True:
+                    retry_count -= 1
+                    self.servers = [server for server in self.servers if
+                                    self.instance_exists(server)]
+                    if not self.servers:
+                        break
+
+                    if retry_count:
+                        LOG.info('    %d yet to be deleted by Nova, retries left=%d...',
+                                 len(self.servers), retry_count)
+                        time.sleep(2)
+                    else:
+                        LOG.warning(
+                            '    instance deletion verification time-out: %d still not deleted',
+                            len(self.servers))
+                        break
 
 
 class NetworkCleaner(object):
@@ -83,7 +96,7 @@ class NetworkCleaner(object):
         for net in all_networks:
             netname = net['name']
             for prefix in network_name_prefixes:
-                if netname.startswith(prefix):
+                if prefix and netname.startswith(prefix):
                     self.networks.append(net)
                     net_ids.append(net['id'])
                     break
@@ -91,28 +104,125 @@ class NetworkCleaner(object):
             LOG.info('Discovering ports...')
             all_ports = self.neutron_client.list_ports()['ports']
             self.ports = [port for port in all_ports if port['network_id'] in net_ids]
+            LOG.info('Discovering floating ips...')
+            all_floating_ips = self.neutron_client.list_floatingips()['floatingips']
+            self.floating_ips = [floating_ip for floating_ip in all_floating_ips if
+                                 floating_ip['floating_network_id'] in net_ids and "nfvbench" in
+                                 floating_ip['description']]
         else:
             self.ports = []
+            self.floating_ips = []
 
     def get_resource_list(self):
         res_list = [["Network", net['name'], net['id']] for net in self.networks]
         res_list.extend([["Port", port['name'], port['id']] for port in self.ports])
+        res_list.extend(
+            [["Floating IP", floating_ip['description'], floating_ip['id']] for floating_ip in
+             self.floating_ips])
         return res_list
 
-    def clean(self):
-        for port in self.ports:
-            LOG.info("Deleting port %s...", port['id'])
-            try:
-                self.neutron_client.delete_port(port['id'])
-            except Exception:
-                LOG.exception("Port deletion failed")
-
-        for net in self.networks:
-            LOG.info("Deleting network %s...", net['name'])
-            try:
-                self.neutron_client.delete_network(net['id'])
-            except Exception:
-                LOG.exception("Network deletion failed")
+    def get_cleaner_code(self):
+        return "networks, ports and floating ips"
+
+    def clean_needed(self, clean_options):
+        if clean_options is None:
+            return True
+        code = self.get_cleaner_code()
+        return code[0] in clean_options
+
+    def clean(self, clean_options):
+        if self.clean_needed(clean_options):
+            for port in self.ports:
+                LOG.info("Deleting port %s...", port['id'])
+                try:
+                    self.neutron_client.delete_port(port['id'])
+                except Exception:
+                    LOG.exception("Port deletion failed")
+            for floating_ip in self.floating_ips:
+                LOG.info("Deleting floating ip %s...", floating_ip['id'])
+                try:
+                    self.neutron_client.delete_floatingip(floating_ip['id'])
+                except Exception:
+                    LOG.exception("Floating IP deletion failed")
+            # associated subnets are automatically deleted by neutron
+            for net in self.networks:
+                LOG.info("Deleting network %s...", net['name'])
+                try:
+                    self.neutron_client.delete_network(net['id'])
+                except Exception:
+                    LOG.exception("Network deletion failed")
+
+
+class RouterCleaner(object):
+    """A cleaner for router resources."""
+
+    def __init__(self, neutron_client, router_names):
+        self.neutron_client = neutron_client
+        LOG.info('Discovering routers...')
+        all_routers = self.neutron_client.list_routers()['routers']
+        self.routers = []
+        self.ports = []
+        self.routes = []
+        rtr_ids = []
+        for rtr in all_routers:
+            rtrname = rtr['name']
+            for name in router_names:
+                if rtrname == name:
+                    self.routers.append(rtr)
+                    rtr_ids.append(rtr['id'])
+
+                    LOG.info('Discovering router routes for router %s...', rtr['name'])
+                    all_routes = rtr['routes']
+                    for route in all_routes:
+                        LOG.info("destination: %s, nexthop: %s", route['destination'],
+                                 route['nexthop'])
+
+                    LOG.info('Discovering router ports for router %s...', rtr['name'])
+                    self.ports.extend(self.neutron_client.list_ports(device_id=rtr['id'])['ports'])
+                    break
+
+    def get_resource_list(self):
+        res_list = [["Router", rtr['name'], rtr['id']] for rtr in self.routers]
+        return res_list
+
+    def get_cleaner_code(self):
+        return "router"
+
+    def clean_needed(self, clean_options):
+        if clean_options is None:
+            return True
+        code = self.get_cleaner_code()
+        return code[0] in clean_options
+
+    def clean(self, clean_options):
+        if self.clean_needed(clean_options):
+            # associated routes needs to be deleted before deleting routers
+            for rtr in self.routers:
+                LOG.info("Deleting routes for %s...", rtr['name'])
+                try:
+                    body = {
+                        'router': {
+                            'routes': []
+                        }
+                    }
+                    self.neutron_client.update_router(rtr['id'], body)
+                except Exception:
+                    LOG.exception("Router routes deletion failed")
+                LOG.info("Deleting ports for %s...", rtr['name'])
+                try:
+                    for port in self.ports:
+                        body = {
+                            'port_id': port['id']
+                        }
+                        self.neutron_client.remove_interface_router(rtr['id'], body)
+                except Exception:
+                    LOG.exception("Router ports deletion failed")
+                LOG.info("Deleting router %s...", rtr['name'])
+                try:
+                    self.neutron_client.delete_router(rtr['id'])
+                except Exception:
+                    LOG.exception("Router deletion failed")
+
 
 class FlavorCleaner(object):
     """Cleaner for NFVbench flavor."""
@@ -130,13 +240,24 @@ class FlavorCleaner(object):
             return [['Flavor', self.name, self.flavor.id]]
         return None
 
-    def clean(self):
-        if self.flavor:
-            LOG.info("Deleting flavor %s...", self.flavor.name)
-            try:
-                self.flavor.delete()
-            except Exception:
-                LOG.exception("Flavor deletion failed")
+    def get_cleaner_code(self):
+        return "flavor"
+
+    def clean_needed(self, clean_options):
+        if clean_options is None:
+            return True
+        code = self.get_cleaner_code()
+        return code[0] in clean_options
+
+    def clean(self, clean_options):
+        if self.clean_needed(clean_options):
+            if self.flavor:
+                LOG.info("Deleting flavor %s...", self.flavor.name)
+                try:
+                    self.flavor.delete()
+                except Exception:
+                    LOG.exception("Flavor deletion failed")
+
 
 class Cleaner(object):
     """Cleaner for all NFVbench resources."""
@@ -147,9 +268,17 @@ class Cleaner(object):
         self.neutron_client = nclient.Client('2.0', session=session)
         self.nova_client = Client(2, session=session)
         network_names = [inet['name'] for inet in config.internal_networks.values()]
+        network_names.extend([inet['name'] for inet in config.edge_networks.values()])
+        network_names.append(config.management_network['name'])
+        network_names.append(config.floating_network['name'])
+        router_names = [rtr['router_name'] for rtr in config.edge_networks.values()]
+        # add idle networks as well
+        if config.idle_networks.name:
+            network_names.append(config.idle_networks.name)
         self.cleaners = [ComputeCleaner(self.nova_client, config.loop_vm_name),
                          FlavorCleaner(self.nova_client, config.flavor_type),
-                         NetworkCleaner(self.neutron_client, network_names)]
+                         NetworkCleaner(self.neutron_client, network_names),
+                         RouterCleaner(self.neutron_client, router_names)]
 
     def show_resources(self):
         """Show all NFVbench resources."""
@@ -168,11 +297,37 @@ class Cleaner(object):
 
     def clean(self, prompt):
         """Clean all resources."""
-        LOG.info("NFVbench will delete all resources shown...")
+        LOG.info("NFVbench will delete resources shown...")
+        clean_options = None
         if prompt:
-            answer = raw_input("Are you sure? (y/n) ")
+            answer = input("Do you want to delete all ressources? (y/n) ")
             if answer.lower() != 'y':
-                LOG.info("Exiting without deleting any resource")
-                sys.exit(0)
+                print("What kind of resources do you want to delete?")
+                all_option = ""
+                all_option_codes = []
+                for cleaner in self.cleaners:
+                    code = cleaner.get_cleaner_code()
+                    print(("%s: %s" % (code[0], code)))
+                    all_option += code[0]
+                    all_option_codes.append(code)
+                print(("a: all resources - a shortcut for '%s'" % all_option))
+                all_option_codes.append("all resources")
+                print("q: quit")
+                answer_res = input(":").lower()
+                # Check only first character because answer_res can be "flavor" and it is != all
+                if answer_res[0] == "a":
+                    clean_options = all_option
+                elif answer_res[0] != 'q':
+                    # if user write complete code instead of shortcuts
+                    # Get only first character of clean code to avoid false clean request
+                    # i.e "networks and ports" and "router" have 1 letter in common and router clean
+                    # will be called even if user ask for networks and ports
+                    if answer_res in all_option_codes:
+                        clean_options = answer_res[0]
+                    else:
+                        clean_options = answer_res
+                else:
+                    LOG.info("Exiting without deleting any resource")
+                    sys.exit(0)
         for cleaner in self.cleaners:
-            cleaner.clean()
+            cleaner.clean(clean_options)