NFVBENCH-161 NFVbench --force-cleanup deletes more ports than needed
[nfvbench.git] / nfvbench / cleanup.py
1 #!/usr/bin/env python
2 # Copyright 2017 Cisco Systems, Inc.  All rights reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15 #
16
17 import sys
18 import time
19
20 from neutronclient.neutron import client as nclient
21 from novaclient.client import Client
22 from novaclient.exceptions import NotFound
23 from tabulate import tabulate
24
25 from . import credentials
26 from .log import LOG
27
28
29 class ComputeCleaner(object):
30     """A cleaner for compute resources."""
31
32     def __init__(self, nova_client, instance_prefix):
33         self.nova_client = nova_client
34         LOG.info('Discovering instances %s...', instance_prefix)
35         all_servers = self.nova_client.servers.list()
36         self.servers = [server for server in all_servers
37                         if server.name.startswith(instance_prefix)]
38
39     def instance_exists(self, server):
40         try:
41             self.nova_client.servers.get(server.id)
42         except NotFound:
43             return False
44         return True
45
46     def get_resource_list(self):
47         return [["Instance", server.name, server.id] for server in self.servers]
48
49     def get_cleaner_code(self):
50         return "instances"
51
52     def clean_needed(self, clean_options):
53         if clean_options is None:
54             return True
55         code = self.get_cleaner_code()
56         return code[0] in clean_options
57
58     def clean(self, clean_options):
59         if self.clean_needed(clean_options):
60             if self.servers:
61                 for server in self.servers:
62                     try:
63                         LOG.info('Deleting instance %s...', server.name)
64                         self.nova_client.servers.delete(server.id)
65                     except Exception:
66                         LOG.exception("Instance %s deletion failed", server.name)
67                 LOG.info('    Waiting for %d instances to be fully deleted...', len(self.servers))
68                 retry_count = 15 + len(self.servers) * 5
69                 while True:
70                     retry_count -= 1
71                     self.servers = [server for server in self.servers if
72                                     self.instance_exists(server)]
73                     if not self.servers:
74                         break
75
76                     if retry_count:
77                         LOG.info('    %d yet to be deleted by Nova, retries left=%d...',
78                                  len(self.servers), retry_count)
79                         time.sleep(2)
80                     else:
81                         LOG.warning(
82                             '    instance deletion verification time-out: %d still not deleted',
83                             len(self.servers))
84                         break
85
86
87 class NetworkCleaner(object):
88     """A cleaner for network resources."""
89
90     def __init__(self, neutron_client, network_name_prefixes):
91         self.neutron_client = neutron_client
92         LOG.info('Discovering networks...')
93         all_networks = self.neutron_client.list_networks()['networks']
94         self.networks = []
95         net_ids = []
96         for net in all_networks:
97             netname = net['name']
98             for prefix in network_name_prefixes:
99                 if prefix and netname.startswith(prefix):
100                     self.networks.append(net)
101                     net_ids.append(net['id'])
102                     break
103         if net_ids:
104             LOG.info('Discovering ports...')
105             all_ports = self.neutron_client.list_ports()['ports']
106             self.ports = [port for port in all_ports if port['network_id'] in net_ids]
107             LOG.info('Discovering floating ips...')
108             all_floating_ips = self.neutron_client.list_floatingips()['floatingips']
109             self.floating_ips = [floating_ip for floating_ip in all_floating_ips if
110                                  floating_ip['floating_network_id'] in net_ids and "nfvbench" in
111                                  floating_ip['description']]
112         else:
113             self.ports = []
114             self.floating_ips = []
115
116     def get_resource_list(self):
117         res_list = [["Network", net['name'], net['id']] for net in self.networks]
118         res_list.extend([["Port", port['name'], port['id']] for port in self.ports])
119         res_list.extend(
120             [["Floating IP", floating_ip['description'], floating_ip['id']] for floating_ip in
121              self.floating_ips])
122         return res_list
123
124     def get_cleaner_code(self):
125         return "networks, ports and floating ips"
126
127     def clean_needed(self, clean_options):
128         if clean_options is None:
129             return True
130         code = self.get_cleaner_code()
131         return code[0] in clean_options
132
133     def clean(self, clean_options):
134         if self.clean_needed(clean_options):
135             for port in self.ports:
136                 LOG.info("Deleting port %s...", port['id'])
137                 try:
138                     self.neutron_client.delete_port(port['id'])
139                 except Exception:
140                     LOG.exception("Port deletion failed")
141             for floating_ip in self.floating_ips:
142                 LOG.info("Deleting floating ip %s...", floating_ip['id'])
143                 try:
144                     self.neutron_client.delete_floatingip(floating_ip['id'])
145                 except Exception:
146                     LOG.exception("Floating IP deletion failed")
147             # associated subnets are automatically deleted by neutron
148             for net in self.networks:
149                 LOG.info("Deleting network %s...", net['name'])
150                 try:
151                     self.neutron_client.delete_network(net['id'])
152                 except Exception:
153                     LOG.exception("Network deletion failed")
154
155
156 class RouterCleaner(object):
157     """A cleaner for router resources."""
158
159     def __init__(self, neutron_client, router_names):
160         self.neutron_client = neutron_client
161         LOG.info('Discovering routers...')
162         all_routers = self.neutron_client.list_routers()['routers']
163         self.routers = []
164         self.ports = []
165         self.routes = []
166         rtr_ids = []
167         for rtr in all_routers:
168             rtrname = rtr['name']
169             for name in router_names:
170                 if rtrname == name:
171                     self.routers.append(rtr)
172                     rtr_ids.append(rtr['id'])
173
174                     LOG.info('Discovering router routes for router %s...', rtr['name'])
175                     all_routes = rtr['routes']
176                     for route in all_routes:
177                         LOG.info("destination: %s, nexthop: %s", route['destination'],
178                                  route['nexthop'])
179
180                     LOG.info('Discovering router ports for router %s...', rtr['name'])
181                     self.ports.extend(self.neutron_client.list_ports(device_id=rtr['id'])['ports'])
182                     break
183
184     def get_resource_list(self):
185         res_list = [["Router", rtr['name'], rtr['id']] for rtr in self.routers]
186         return res_list
187
188     def get_cleaner_code(self):
189         return "router"
190
191     def clean_needed(self, clean_options):
192         if clean_options is None:
193             return True
194         code = self.get_cleaner_code()
195         return code[0] in clean_options
196
197     def clean(self, clean_options):
198         if self.clean_needed(clean_options):
199             # associated routes needs to be deleted before deleting routers
200             for rtr in self.routers:
201                 LOG.info("Deleting routes for %s...", rtr['name'])
202                 try:
203                     body = {
204                         'router': {
205                             'routes': []
206                         }
207                     }
208                     self.neutron_client.update_router(rtr['id'], body)
209                 except Exception:
210                     LOG.exception("Router routes deletion failed")
211                 LOG.info("Deleting ports for %s...", rtr['name'])
212                 try:
213                     for port in self.ports:
214                         body = {
215                             'port_id': port['id']
216                         }
217                         self.neutron_client.remove_interface_router(rtr['id'], body)
218                 except Exception:
219                     LOG.exception("Router ports deletion failed")
220                 LOG.info("Deleting router %s...", rtr['name'])
221                 try:
222                     self.neutron_client.delete_router(rtr['id'])
223                 except Exception:
224                     LOG.exception("Router deletion failed")
225
226
227 class FlavorCleaner(object):
228     """Cleaner for NFVbench flavor."""
229
230     def __init__(self, nova_client, name):
231         self.name = name
232         LOG.info('Discovering flavor %s...', name)
233         try:
234             self.flavor = nova_client.flavors.find(name=name)
235         except NotFound:
236             self.flavor = None
237
238     def get_resource_list(self):
239         if self.flavor:
240             return [['Flavor', self.name, self.flavor.id]]
241         return None
242
243     def get_cleaner_code(self):
244         return "flavor"
245
246     def clean_needed(self, clean_options):
247         if clean_options is None:
248             return True
249         code = self.get_cleaner_code()
250         return code[0] in clean_options
251
252     def clean(self, clean_options):
253         if self.clean_needed(clean_options):
254             if self.flavor:
255                 LOG.info("Deleting flavor %s...", self.flavor.name)
256                 try:
257                     self.flavor.delete()
258                 except Exception:
259                     LOG.exception("Flavor deletion failed")
260
261
262 class Cleaner(object):
263     """Cleaner for all NFVbench resources."""
264
265     def __init__(self, config):
266         cred = credentials.Credentials(config.openrc_file, None, False)
267         session = cred.get_session()
268         self.neutron_client = nclient.Client('2.0', session=session)
269         self.nova_client = Client(2, session=session)
270         network_names = [inet['name'] for inet in config.internal_networks.values()]
271         network_names.extend([inet['name'] for inet in config.edge_networks.values()])
272         network_names.append(config.management_network['name'])
273         network_names.append(config.floating_network['name'])
274         router_names = [rtr['router_name'] for rtr in config.edge_networks.values()]
275         # add idle networks as well
276         if config.idle_networks.name:
277             network_names.append(config.idle_networks.name)
278         self.cleaners = [ComputeCleaner(self.nova_client, config.loop_vm_name),
279                          FlavorCleaner(self.nova_client, config.flavor_type),
280                          NetworkCleaner(self.neutron_client, network_names),
281                          RouterCleaner(self.neutron_client, router_names)]
282
283     def show_resources(self):
284         """Show all NFVbench resources."""
285         table = [["Type", "Name", "UUID"]]
286         for cleaner in self.cleaners:
287             res_list = cleaner.get_resource_list()
288             if res_list:
289                 table.extend(res_list)
290         count = len(table) - 1
291         if count:
292             LOG.info('Discovered %d NFVbench resources:\n%s', count,
293                      tabulate(table, headers="firstrow", tablefmt="psql"))
294         else:
295             LOG.info('No matching NFVbench resources found')
296         return count
297
298     def clean(self, prompt):
299         """Clean all resources."""
300         LOG.info("NFVbench will delete resources shown...")
301         clean_options = None
302         if prompt:
303             answer = input("Do you want to delete all ressources? (y/n) ")
304             if answer.lower() != 'y':
305                 print("What kind of resources do you want to delete?")
306                 all_option = ""
307                 all_option_codes = []
308                 for cleaner in self.cleaners:
309                     code = cleaner.get_cleaner_code()
310                     print(("%s: %s" % (code[0], code)))
311                     all_option += code[0]
312                     all_option_codes.append(code)
313                 print(("a: all resources - a shortcut for '%s'" % all_option))
314                 all_option_codes.append("all resources")
315                 print("q: quit")
316                 answer_res = input(":").lower()
317                 # Check only first character because answer_res can be "flavor" and it is != all
318                 if answer_res[0] == "a":
319                     clean_options = all_option
320                 elif answer_res[0] != 'q':
321                     # if user write complete code instead of shortcuts
322                     # Get only first character of clean code to avoid false clean request
323                     # i.e "networks and ports" and "router" have 1 letter in common and router clean
324                     # will be called even if user ask for networks and ports
325                     if answer_res in all_option_codes:
326                         clean_options = answer_res[0]
327                     else:
328                         clean_options = answer_res
329                 else:
330                     LOG.info("Exiting without deleting any resource")
331                     sys.exit(0)
332         for cleaner in self.cleaners:
333             cleaner.clean(clean_options)