NFVBENCH-200 Cleanup refactoring
[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
19 from neutronclient.neutron import client as nclient
20 from novaclient.client import Client
21 from novaclient.exceptions import NotFound
22 from tabulate import tabulate
23
24 from . import credentials
25 from .log import LOG
26 from . import utils
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 get_resource_list(self):
40         return [["Instance", server.name, server.id] for server in self.servers]
41
42     def get_cleaner_code(self):
43         return "instances"
44
45     def clean_needed(self, clean_options):
46         if clean_options is None:
47             return True
48         code = self.get_cleaner_code()
49         return code[0] in clean_options
50
51     def clean(self, clean_options):
52         if self.clean_needed(clean_options):
53             if self.servers:
54                 for server in self.servers:
55                     utils.delete_server(self.nova_client, server)
56                 utils.waiting_servers_deletion(self.nova_client, self.servers)
57
58
59 class NetworkCleaner(object):
60     """A cleaner for network resources."""
61
62     def __init__(self, neutron_client, network_name_prefixes):
63         self.neutron_client = neutron_client
64         LOG.info('Discovering networks...')
65         all_networks = self.neutron_client.list_networks()['networks']
66         self.networks = []
67         net_ids = []
68         for net in all_networks:
69             netname = net['name']
70             for prefix in network_name_prefixes:
71                 if prefix and netname.startswith(prefix):
72                     self.networks.append(net)
73                     net_ids.append(net['id'])
74                     break
75         if net_ids:
76             LOG.info('Discovering ports...')
77             all_ports = self.neutron_client.list_ports()['ports']
78             self.ports = [port for port in all_ports if port['network_id'] in net_ids]
79             LOG.info('Discovering floating ips...')
80             all_floating_ips = self.neutron_client.list_floatingips()['floatingips']
81             self.floating_ips = [floating_ip for floating_ip in all_floating_ips if
82                                  floating_ip['floating_network_id'] in net_ids and "nfvbench" in
83                                  floating_ip['description']]
84         else:
85             self.ports = []
86             self.floating_ips = []
87
88     def get_resource_list(self):
89         res_list = [["Network", net['name'], net['id']] for net in self.networks]
90         res_list.extend([["Port", port['name'], port['id']] for port in self.ports])
91         res_list.extend(
92             [["Floating IP", floating_ip['description'], floating_ip['id']] for floating_ip in
93              self.floating_ips])
94         return res_list
95
96     def get_cleaner_code(self):
97         return "networks, ports and floating ips"
98
99     def clean_needed(self, clean_options):
100         if clean_options is None:
101             return True
102         code = self.get_cleaner_code()
103         return code[0] in clean_options
104
105     def clean(self, clean_options):
106         if self.clean_needed(clean_options):
107             for port in self.ports:
108                 LOG.info("Deleting port %s...", port['id'])
109                 try:
110                     self.neutron_client.delete_port(port['id'])
111                 except Exception:
112                     LOG.exception("Port deletion failed")
113             for floating_ip in self.floating_ips:
114                 LOG.info("Deleting floating ip %s...", floating_ip['id'])
115                 try:
116                     self.neutron_client.delete_floatingip(floating_ip['id'])
117                 except Exception:
118                     LOG.exception("Floating IP deletion failed")
119             # associated subnets are automatically deleted by neutron
120             for net in self.networks:
121                 LOG.info("Deleting network %s...", net['name'])
122                 try:
123                     self.neutron_client.delete_network(net['id'])
124                 except Exception:
125                     LOG.exception("Network deletion failed")
126
127
128 class RouterCleaner(object):
129     """A cleaner for router resources."""
130
131     def __init__(self, neutron_client, router_names):
132         self.neutron_client = neutron_client
133         LOG.info('Discovering routers...')
134         all_routers = self.neutron_client.list_routers()['routers']
135         self.routers = []
136         self.ports = []
137         self.routes = []
138         rtr_ids = []
139         for rtr in all_routers:
140             rtrname = rtr['name']
141             for name in router_names:
142                 if rtrname == name:
143                     self.routers.append(rtr)
144                     rtr_ids.append(rtr['id'])
145
146                     LOG.info('Discovering router routes for router %s...', rtr['name'])
147                     all_routes = rtr['routes']
148                     for route in all_routes:
149                         LOG.info("destination: %s, nexthop: %s", route['destination'],
150                                  route['nexthop'])
151
152                     LOG.info('Discovering router ports for router %s...', rtr['name'])
153                     self.ports.extend(self.neutron_client.list_ports(device_id=rtr['id'])['ports'])
154                     break
155
156     def get_resource_list(self):
157         res_list = [["Router", rtr['name'], rtr['id']] for rtr in self.routers]
158         return res_list
159
160     def get_cleaner_code(self):
161         return "router"
162
163     def clean_needed(self, clean_options):
164         if clean_options is None:
165             return True
166         code = self.get_cleaner_code()
167         return code[0] in clean_options
168
169     def clean(self, clean_options):
170         if self.clean_needed(clean_options):
171             # associated routes needs to be deleted before deleting routers
172             for rtr in self.routers:
173                 LOG.info("Deleting routes for %s...", rtr['name'])
174                 try:
175                     body = {
176                         'router': {
177                             'routes': []
178                         }
179                     }
180                     self.neutron_client.update_router(rtr['id'], body)
181                 except Exception:
182                     LOG.exception("Router routes deletion failed")
183                 LOG.info("Deleting ports for %s...", rtr['name'])
184                 try:
185                     for port in self.ports:
186                         body = {
187                             'port_id': port['id']
188                         }
189                         self.neutron_client.remove_interface_router(rtr['id'], body)
190                 except Exception:
191                     LOG.exception("Router ports deletion failed")
192                 LOG.info("Deleting router %s...", rtr['name'])
193                 try:
194                     self.neutron_client.delete_router(rtr['id'])
195                 except Exception:
196                     LOG.exception("Router deletion failed")
197
198
199 class FlavorCleaner(object):
200     """Cleaner for NFVbench flavor."""
201
202     def __init__(self, nova_client, name):
203         self.name = name
204         LOG.info('Discovering flavor %s...', name)
205         try:
206             self.flavor = nova_client.flavors.find(name=name)
207         except NotFound:
208             self.flavor = None
209
210     def get_resource_list(self):
211         if self.flavor:
212             return [['Flavor', self.name, self.flavor.id]]
213         return None
214
215     def get_cleaner_code(self):
216         return "flavor"
217
218     def clean_needed(self, clean_options):
219         if clean_options is None:
220             return True
221         code = self.get_cleaner_code()
222         return code[0] in clean_options
223
224     def clean(self, clean_options):
225         if self.clean_needed(clean_options):
226             if self.flavor:
227                 LOG.info("Deleting flavor %s...", self.flavor.name)
228                 try:
229                     self.flavor.delete()
230                 except Exception:
231                     LOG.exception("Flavor deletion failed")
232
233
234 class Cleaner(object):
235     """Cleaner for all NFVbench resources."""
236
237     def __init__(self, config):
238         cred = credentials.Credentials(config.openrc_file, None, False)
239         session = cred.get_session()
240         self.neutron_client = nclient.Client('2.0', session=session)
241         self.nova_client = Client(2, session=session)
242         network_names = [inet['name'] for inet in config.internal_networks.values()]
243         network_names.extend([inet['name'] for inet in config.edge_networks.values()])
244         network_names.append(config.management_network['name'])
245         network_names.append(config.floating_network['name'])
246         router_names = [rtr['router_name'] for rtr in config.edge_networks.values()]
247         # add idle networks as well
248         if config.idle_networks.name:
249             network_names.append(config.idle_networks.name)
250         self.cleaners = [ComputeCleaner(self.nova_client, config.loop_vm_name),
251                          FlavorCleaner(self.nova_client, config.flavor_type),
252                          NetworkCleaner(self.neutron_client, network_names),
253                          RouterCleaner(self.neutron_client, router_names)]
254
255     def show_resources(self):
256         """Show all NFVbench resources."""
257         table = [["Type", "Name", "UUID"]]
258         for cleaner in self.cleaners:
259             res_list = cleaner.get_resource_list()
260             if res_list:
261                 table.extend(res_list)
262         count = len(table) - 1
263         if count:
264             LOG.info('Discovered %d NFVbench resources:\n%s', count,
265                      tabulate(table, headers="firstrow", tablefmt="psql"))
266         else:
267             LOG.info('No matching NFVbench resources found')
268         return count
269
270     def clean(self, prompt):
271         """Clean all resources."""
272         LOG.info("NFVbench will delete resources shown...")
273         clean_options = None
274         if prompt:
275             answer = input("Do you want to delete all ressources? (y/n) ")
276             if answer.lower() != 'y':
277                 print("What kind of resources do you want to delete?")
278                 all_option = ""
279                 all_option_codes = []
280                 for cleaner in self.cleaners:
281                     code = cleaner.get_cleaner_code()
282                     print(("%s: %s" % (code[0], code)))
283                     all_option += code[0]
284                     all_option_codes.append(code)
285                 print(("a: all resources - a shortcut for '%s'" % all_option))
286                 all_option_codes.append("all resources")
287                 print("q: quit")
288                 answer_res = input(":").lower()
289                 # Check only first character because answer_res can be "flavor" and it is != all
290                 if answer_res[0] == "a":
291                     clean_options = all_option
292                 elif answer_res[0] != 'q':
293                     # if user write complete code instead of shortcuts
294                     # Get only first character of clean code to avoid false clean request
295                     # i.e "networks and ports" and "router" have 1 letter in common and router clean
296                     # will be called even if user ask for networks and ports
297                     if answer_res in all_option_codes:
298                         clean_options = answer_res[0]
299                     else:
300                         clean_options = answer_res
301                 else:
302                     LOG.info("Exiting without deleting any resource")
303                     sys.exit(0)
304         for cleaner in self.cleaners:
305             cleaner.clean(clean_options)