Add L3 traffic management with Neutron routers
[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 import credentials as 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 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         else:
108             self.ports = []
109
110     def get_resource_list(self):
111         res_list = [["Network", net['name'], net['id']] for net in self.networks]
112         res_list.extend([["Port", port['name'], port['id']] for port in self.ports])
113         return res_list
114
115     def get_cleaner_code(self):
116         return "networks and ports"
117
118     def clean_needed(self, clean_options):
119         if clean_options is None:
120             return True
121         code = self.get_cleaner_code()
122         return code[0] in clean_options
123
124     def clean(self, clean_options):
125         if self.clean_needed(clean_options):
126             for port in self.ports:
127                 LOG.info("Deleting port %s...", port['id'])
128                 try:
129                     self.neutron_client.delete_port(port['id'])
130                 except Exception:
131                     LOG.exception("Port deletion failed")
132
133             # associated subnets are automatically deleted by neutron
134             for net in self.networks:
135                 LOG.info("Deleting network %s...", net['name'])
136                 try:
137                     self.neutron_client.delete_network(net['id'])
138                 except Exception:
139                     LOG.exception("Network deletion failed")
140
141
142 class RouterCleaner(object):
143     """A cleaner for router resources."""
144
145     def __init__(self, neutron_client, router_names):
146         self.neutron_client = neutron_client
147         LOG.info('Discovering routers...')
148         all_routers = self.neutron_client.list_routers()['routers']
149         self.routers = []
150         self.ports = []
151         self.routes = []
152         rtr_ids = []
153         for rtr in all_routers:
154             rtrname = rtr['name']
155             for name in router_names:
156                 if rtrname == name:
157                     self.routers.append(rtr)
158                     rtr_ids.append(rtr['id'])
159
160                     LOG.info('Discovering router routes for router %s...', rtr['name'])
161                     all_routes = rtr['routes']
162                     for route in all_routes:
163                         LOG.info("destination: %s, nexthop: %s", route['destination'],
164                                  route['nexthop'])
165
166                     LOG.info('Discovering router ports for router %s...', rtr['name'])
167                     self.ports.extend(self.neutron_client.list_ports(device_id=rtr['id'])['ports'])
168                     break
169
170     def get_resource_list(self):
171         res_list = [["Router", rtr['name'], rtr['id']] for rtr in self.routers]
172         return res_list
173
174     def get_cleaner_code(self):
175         return "router"
176
177     def clean_needed(self, clean_options):
178         if clean_options is None:
179             return True
180         code = self.get_cleaner_code()
181         return code[0] in clean_options
182
183     def clean(self, clean_options):
184         if self.clean_needed(clean_options):
185             # associated routes needs to be deleted before deleting routers
186             for rtr in self.routers:
187                 LOG.info("Deleting routes for %s...", rtr['name'])
188                 try:
189                     body = {
190                         'router': {
191                             'routes': []
192                         }
193                     }
194                     self.neutron_client.update_router(rtr['id'], body)
195                 except Exception:
196                     LOG.exception("Router routes deletion failed")
197                 LOG.info("Deleting ports for %s...", rtr['name'])
198                 try:
199                     for port in self.ports:
200                         body = {
201                             'port_id': port['id']
202                         }
203                         self.neutron_client.remove_interface_router(rtr['id'], body)
204                 except Exception:
205                     LOG.exception("Router ports deletion failed")
206                 LOG.info("Deleting router %s...", rtr['name'])
207                 try:
208                     self.neutron_client.delete_router(rtr['id'])
209                 except Exception:
210                     LOG.exception("Router deletion failed")
211
212
213 class FlavorCleaner(object):
214     """Cleaner for NFVbench flavor."""
215
216     def __init__(self, nova_client, name):
217         self.name = name
218         LOG.info('Discovering flavor %s...', name)
219         try:
220             self.flavor = nova_client.flavors.find(name=name)
221         except NotFound:
222             self.flavor = None
223
224     def get_resource_list(self):
225         if self.flavor:
226             return [['Flavor', self.name, self.flavor.id]]
227         return None
228
229     def get_cleaner_code(self):
230         return "flavor"
231
232     def clean_needed(self, clean_options):
233         if clean_options is None:
234             return True
235         code = self.get_cleaner_code()
236         return code[0] in clean_options
237
238     def clean(self, clean_options):
239         if self.clean_needed(clean_options):
240             if self.flavor:
241                 LOG.info("Deleting flavor %s...", self.flavor.name)
242                 try:
243                     self.flavor.delete()
244                 except Exception:
245                     LOG.exception("Flavor deletion failed")
246
247
248 class Cleaner(object):
249     """Cleaner for all NFVbench resources."""
250
251     def __init__(self, config):
252         cred = credentials.Credentials(config.openrc_file, None, False)
253         session = cred.get_session()
254         self.neutron_client = nclient.Client('2.0', session=session)
255         self.nova_client = Client(2, session=session)
256         network_names = [inet['name'] for inet in config.internal_networks.values()]
257         network_names.extend([inet['name'] for inet in config.edge_networks.values()])
258         router_names = [rtr['router_name'] for rtr in config.edge_networks.values()]
259         # add idle networks as well
260         if config.idle_networks.name:
261             network_names.append(config.idle_networks.name)
262         self.cleaners = [ComputeCleaner(self.nova_client, config.loop_vm_name),
263                          FlavorCleaner(self.nova_client, config.flavor_type),
264                          NetworkCleaner(self.neutron_client, network_names),
265                          RouterCleaner(self.neutron_client, router_names)]
266
267     def show_resources(self):
268         """Show all NFVbench resources."""
269         table = [["Type", "Name", "UUID"]]
270         for cleaner in self.cleaners:
271             res_list = cleaner.get_resource_list()
272             if res_list:
273                 table.extend(res_list)
274         count = len(table) - 1
275         if count:
276             LOG.info('Discovered %d NFVbench resources:\n%s', count,
277                      tabulate(table, headers="firstrow", tablefmt="psql"))
278         else:
279             LOG.info('No matching NFVbench resources found')
280         return count
281
282     def clean(self, prompt):
283         """Clean all resources."""
284         LOG.info("NFVbench will delete resources shown...")
285         clean_options = None
286         if prompt:
287             answer = raw_input("Do you want to delete all ressources? (y/n) ")
288             if answer.lower() != 'y':
289                 print "What kind of resources do you want to delete?"
290                 all_option = ""
291                 all_option_codes = []
292                 for cleaner in self.cleaners:
293                     code = cleaner.get_cleaner_code()
294                     print "%s: %s" % (code[0], code)
295                     all_option += code[0]
296                     all_option_codes.append(code)
297                 print "a: all resources - a shortcut for '%s'" % all_option
298                 all_option_codes.append("all resources")
299                 print "q: quit"
300                 answer_res = raw_input(":").lower()
301                 # Check only first character because answer_res can be "flavor" and it is != all
302                 if answer_res[0] == "a":
303                     clean_options = all_option
304                 elif answer_res[0] != 'q':
305                     # if user write complete code instead of shortcuts
306                     # Get only first character of clean code to avoid false clean request
307                     # i.e "networks and ports" and "router" have 1 letter in common and router clean
308                     # will be called even if user ask for networks and ports
309                     if answer_res in all_option_codes:
310                         clean_options = answer_res[0]
311                     else:
312                         clean_options = answer_res
313                 else:
314                     LOG.info("Exiting without deleting any resource")
315                     sys.exit(0)
316         for cleaner in self.cleaners:
317             cleaner.clean(clean_options)