Use neutron to create floating IPs.
[snaps.git] / snaps / openstack / utils / neutron_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain 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,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import logging
16
17 from neutronclient.common.exceptions import NotFound
18 from neutronclient.neutron.client import Client
19 from snaps.domain.vm_inst import FloatingIp
20 from snaps.openstack.utils import keystone_utils
21
22 __author__ = 'spisarski'
23
24 logger = logging.getLogger('neutron_utils')
25
26 """
27 Utilities for basic neutron API calls
28 """
29
30
31 def neutron_client(os_creds):
32     """
33     Instantiates and returns a client for communications with OpenStack's
34     Neutron server
35     :param os_creds: the credentials for connecting to the OpenStack remote API
36     :return: the client object
37     """
38     return Client(api_version=os_creds.network_api_version,
39                   session=keystone_utils.keystone_session(os_creds))
40
41
42 def create_network(neutron, os_creds, network_settings):
43     """
44     Creates a network for OpenStack
45     :param neutron: the client
46     :param os_creds: the OpenStack credentials
47     :param network_settings: A dictionary containing the network configuration
48                              and is responsible for creating the network
49                             request JSON body
50     :return: the network object
51     """
52     if neutron and network_settings:
53         logger.info('Creating network with name ' + network_settings.name)
54         json_body = network_settings.dict_for_neutron(os_creds)
55         return neutron.create_network(body=json_body)
56     else:
57         logger.error("Failed to create network")
58         raise Exception
59
60
61 def delete_network(neutron, network):
62     """
63     Deletes a network for OpenStack
64     :param neutron: the client
65     :param network: the network object
66     """
67     if neutron and network:
68         logger.info('Deleting network with name ' + network['network']['name'])
69         neutron.delete_network(network['network']['id'])
70
71
72 def get_network(neutron, network_name, project_id=None):
73     """
74     Returns an object (dictionary) of the first network found with a given name
75     and project_id (if included)
76     :param neutron: the client
77     :param network_name: the name of the network to retrieve
78     :param project_id: the id of the network's project
79     :return:
80     """
81     net_filter = dict()
82     if network_name:
83         net_filter['name'] = network_name
84     if project_id:
85         net_filter['project_id'] = project_id
86
87     networks = neutron.list_networks(**net_filter)
88     for network, netInsts in networks.items():
89         for inst in netInsts:
90             if inst.get('name') == network_name:
91                 if project_id and inst.get('project_id') == project_id:
92                     return {'network': inst}
93                 else:
94                     return {'network': inst}
95     return None
96
97
98 def get_network_by_id(neutron, network_id):
99     """
100     Returns the network object (dictionary) with the given ID
101     :param neutron: the client
102     :param network_id: the id of the network to retrieve
103     :return:
104     """
105     networks = neutron.list_networks(**{'id': network_id})
106     for network, netInsts in networks.items():
107         for inst in netInsts:
108             if inst.get('id') == network_id:
109                 return {'network': inst}
110     return None
111
112
113 def create_subnet(neutron, subnet_settings, os_creds, network=None):
114     """
115     Creates a network subnet for OpenStack
116     :param neutron: the client
117     :param network: the network object
118     :param subnet_settings: A dictionary containing the subnet configuration
119                             and is responsible for creating the subnet request
120                             JSON body
121     :param os_creds: the OpenStack credentials
122     :return: the subnet object
123     """
124     if neutron and network and subnet_settings:
125         json_body = {'subnets': [subnet_settings.dict_for_neutron(
126             os_creds, network=network)]}
127         logger.info('Creating subnet with name ' + subnet_settings.name)
128         subnets = neutron.create_subnet(body=json_body)
129         return {'subnet': subnets['subnets'][0]}
130     else:
131         logger.error("Failed to create subnet.")
132         raise Exception
133
134
135 def delete_subnet(neutron, subnet):
136     """
137     Deletes a network subnet for OpenStack
138     :param neutron: the client
139     :param subnet: the subnet object
140     """
141     if neutron and subnet:
142         logger.info('Deleting subnet with name ' + subnet['subnet']['name'])
143         neutron.delete_subnet(subnet['subnet']['id'])
144
145
146 def get_subnet_by_name(neutron, subnet_name):
147     """
148     Returns the first subnet object (dictionary) found with a given name
149     :param neutron: the client
150     :param subnet_name: the name of the network to retrieve
151     :return:
152     """
153     subnets = neutron.list_subnets(**{'name': subnet_name})
154     for subnet, subnetInst in subnets.items():
155         for inst in subnetInst:
156             if inst.get('name') == subnet_name:
157                 return {'subnet': inst}
158     return None
159
160
161 def create_router(neutron, os_creds, router_settings):
162     """
163     Creates a router for OpenStack
164     :param neutron: the client
165     :param os_creds: the OpenStack credentials
166     :param router_settings: A dictionary containing the router configuration
167                             and is responsible for creating the subnet request
168                             JSON body
169     :return: the router object
170     """
171     if neutron:
172         json_body = router_settings.dict_for_neutron(neutron, os_creds)
173         logger.info('Creating router with name - ' + router_settings.name)
174         return neutron.create_router(json_body)
175     else:
176         logger.error("Failed to create router.")
177         raise Exception
178
179
180 def delete_router(neutron, router):
181     """
182     Deletes a router for OpenStack
183     :param neutron: the client
184     :param router: the router object
185     """
186     if neutron and router:
187         logger.info('Deleting router with name - ' + router['router']['name'])
188         neutron.delete_router(router=router['router']['id'])
189         return True
190
191
192 def get_router_by_name(neutron, router_name):
193     """
194     Returns the first router object (dictionary) found with a given name
195     :param neutron: the client
196     :param router_name: the name of the network to retrieve
197     :return:
198     """
199     routers = neutron.list_routers(**{'name': router_name})
200     for router, routerInst in routers.items():
201         for inst in routerInst:
202             if inst.get('name') == router_name:
203                 return {'router': inst}
204     return None
205
206
207 def add_interface_router(neutron, router, subnet=None, port=None):
208     """
209     Adds an interface router for OpenStack for either a subnet or port.
210     Exception will be raised if requesting for both.
211     :param neutron: the client
212     :param router: the router object
213     :param subnet: the subnet object
214     :param port: the port object
215     :return: the interface router object
216     """
217     if subnet and port:
218         raise Exception('Cannot add interface to the router. Both subnet and '
219                         'port were sent in. Either or please.')
220
221     if neutron and router and (router or subnet):
222         logger.info('Adding interface to router with name ' +
223                     router['router']['name'])
224         return neutron.add_interface_router(
225             router=router['router']['id'],
226             body=__create_port_json_body(subnet, port))
227     else:
228         raise Exception('Unable to create interface router as neutron client,'
229                         ' router or subnet were not created')
230
231
232 def remove_interface_router(neutron, router, subnet=None, port=None):
233     """
234     Removes an interface router for OpenStack
235     :param neutron: the client
236     :param router: the router object
237     :param subnet: the subnet object (either subnet or port, not both)
238     :param port: the port object
239     """
240     if router:
241         try:
242             logger.info('Removing router interface from router named ' +
243                         router['router']['name'])
244             neutron.remove_interface_router(
245                 router=router['router']['id'],
246                 body=__create_port_json_body(subnet, port))
247         except NotFound as e:
248             logger.warning('Could not remove router interface. NotFound - %s',
249                            e)
250             pass
251     else:
252         logger.warning('Could not remove router interface, No router object')
253
254
255 def __create_port_json_body(subnet=None, port=None):
256     """
257     Returns the dictionary required for creating and deleting router
258     interfaces. Will only work on a subnet or port object. Will throw and
259     exception if parameters contain both or neither
260     :param subnet: the subnet object
261     :param port: the port object
262     :return: the dict
263     """
264     if subnet and port:
265         raise Exception('Cannot create JSON body with both subnet and port')
266     if not subnet and not port:
267         raise Exception('Cannot create JSON body without subnet or port')
268
269     if subnet:
270         return {"subnet_id": subnet['subnet']['id']}
271     else:
272         return {"port_id": port['port']['id']}
273
274
275 def create_port(neutron, os_creds, port_settings):
276     """
277     Creates a port for OpenStack
278     :param neutron: the client
279     :param os_creds: the OpenStack credentials
280     :param port_settings: the settings object for port configuration
281     :return: the port object
282     """
283     json_body = port_settings.dict_for_neutron(neutron, os_creds)
284     logger.info('Creating port for network with name - %s',
285                 port_settings.network_name)
286     return neutron.create_port(body=json_body)
287
288
289 def delete_port(neutron, port):
290     """
291     Removes an OpenStack port
292     :param neutron: the client
293     :param port: the port object
294     :return:
295     """
296     logger.info('Deleting port with name ' + port['port']['name'])
297     neutron.delete_port(port['port']['id'])
298
299
300 def get_port_by_name(neutron, port_name):
301     """
302     Returns the first port object (dictionary) found with a given name
303     :param neutron: the client
304     :param port_name: the name of the port to retrieve
305     :return:
306     """
307     ports = neutron.list_ports(**{'name': port_name})
308     for port in ports['ports']:
309         if port['name'] == port_name:
310             return {'port': port}
311     return None
312
313
314 def create_security_group(neutron, keystone, sec_grp_settings):
315     """
316     Creates a security group object in OpenStack
317     :param neutron: the Neutron client
318     :param keystone: the Keystone client
319     :param sec_grp_settings: the security group settings
320     :return: the security group object
321     """
322     logger.info('Creating security group with name - %s',
323                 sec_grp_settings.name)
324     return neutron.create_security_group(
325         sec_grp_settings.dict_for_neutron(keystone))
326
327
328 def delete_security_group(neutron, sec_grp):
329     """
330     Deletes a security group object from OpenStack
331     :param neutron: the client
332     :param sec_grp: the security group object to delete
333     """
334     logger.info('Deleting security group with name - %s',
335                 sec_grp['security_group']['name'])
336     return neutron.delete_security_group(sec_grp['security_group']['id'])
337
338
339 def get_security_group(neutron, name):
340     """
341     Returns the first security group object of the given name else None
342     :param neutron: the client
343     :param name: the name of security group object to retrieve
344     """
345     logger.info('Retrieving security group with name - ' + name)
346
347     groups = neutron.list_security_groups(**{'name': name})
348     for group in groups['security_groups']:
349         if group['name'] == name:
350             return {'security_group': group}
351     return None
352
353
354 def get_security_group_by_id(neutron, sec_grp_id):
355     """
356     Returns the first security group object of the given name else None
357     :param neutron: the client
358     :param sec_grp_id: the id of the security group to retrieve
359     """
360     logger.info('Retrieving security group with ID - ' + sec_grp_id)
361
362     groups = neutron.list_security_groups(**{'id': sec_grp_id})
363     for group in groups['security_groups']:
364         if group['id'] == sec_grp_id:
365             return {'security_group': group}
366     return None
367
368
369 def create_security_group_rule(neutron, sec_grp_rule_settings):
370     """
371     Creates a security group object in OpenStack
372     :param neutron: the client
373     :param sec_grp_rule_settings: the security group rule settings
374     :return: the security group object
375     """
376     logger.info('Creating security group to security group - %s',
377                 sec_grp_rule_settings.sec_grp_name)
378     return neutron.create_security_group_rule(
379         sec_grp_rule_settings.dict_for_neutron(neutron))
380
381
382 def delete_security_group_rule(neutron, sec_grp_rule):
383     """
384     Deletes a security group object from OpenStack
385     :param neutron: the client
386     :param sec_grp_rule: the security group rule object to delete
387     """
388     logger.info('Deleting security group rule with ID - %s',
389                 sec_grp_rule['security_group_rule']['id'])
390     neutron.delete_security_group_rule(
391         sec_grp_rule['security_group_rule']['id'])
392
393
394 def get_rules_by_security_group(neutron, sec_grp):
395     """
396     Retrieves all of the rules for a given security group
397     :param neutron: the client
398     :param sec_grp: the security group object
399     """
400     logger.info('Retrieving security group rules associate with the '
401                 'security group - %s', sec_grp['security_group']['name'])
402     out = list()
403     rules = neutron.list_security_group_rules(
404         **{'security_group_id': sec_grp['security_group']['id']})
405     for rule in rules['security_group_rules']:
406         if rule['security_group_id'] == sec_grp['security_group']['id']:
407             out.append({'security_group_rule': rule})
408     return out
409
410
411 def get_rule_by_id(neutron, sec_grp, rule_id):
412     """
413     Deletes a security group object from OpenStack
414     :param neutron: the client
415     :param sec_grp: the security group object
416     :param rule_id: the rule's ID
417     """
418     rules = neutron.list_security_group_rules(
419         **{'security_group_id': sec_grp['security_group']['id']})
420     for rule in rules['security_group_rules']:
421         if rule['id'] == rule_id:
422             return {'security_group_rule': rule}
423     return None
424
425
426 def get_external_networks(neutron):
427     """
428     Returns a list of external OpenStack network object/dict for all external
429     networks
430     :param neutron: the client
431     :return: a list of external networks (empty list if none configured)
432     """
433     out = list()
434     for network in neutron.list_networks(
435             **{'router:external': True})['networks']:
436         out.append({'network': network})
437     return out
438
439
440 def get_floating_ips(neutron):
441     """
442     Returns all of the floating IPs
443     :param neutron: the Neutron client
444     :return: a list of SNAPS FloatingIp objects
445     """
446     out = list()
447     fips = neutron.list_floatingips()
448     for fip in fips['floatingips']:
449         out.append(FloatingIp(inst_id=fip['id'],
450                               ip=fip['floating_ip_address']))
451
452     return out
453
454
455 def create_floating_ip(neutron, ext_net_name):
456     """
457     Returns the floating IP object that was created with this call
458     :param neutron: the Neutron client
459     :param ext_net_name: the name of the external network on which to apply the
460                          floating IP address
461     :return: the SNAPS FloatingIp object
462     """
463     logger.info('Creating floating ip to external network - ' + ext_net_name)
464     ext_net = get_network(neutron, ext_net_name)
465     if ext_net:
466         fip = neutron.create_floatingip(
467             body={'floatingip':
468                   {'floating_network_id': ext_net['network']['id']}})
469
470         return FloatingIp(inst_id=fip['floatingip']['id'],
471                           ip=fip['floatingip']['floating_ip_address'])
472     else:
473         raise Exception('Cannot create floating IP, '
474                         'external network not found')
475
476
477 def get_floating_ip(neutron, floating_ip):
478     """
479     Returns a floating IP object that should be identical to the floating_ip
480     parameter
481     :param neutron: the Neutron client
482     :param floating_ip: the SNAPS FloatingIp object
483     :return: hopefully the same floating IP object input
484     """
485     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
486                  floating_ip.ip)
487     os_fip = get_os_floating_ip(neutron, floating_ip)
488     if os_fip:
489         return FloatingIp(
490             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
491
492
493 def get_os_floating_ip(neutron, floating_ip):
494     """
495     Returns an OpenStack floating IP object
496     parameter
497     :param neutron: the Neutron client
498     :param floating_ip: the SNAPS FloatingIp object
499     :return: hopefully the same floating IP object input
500     """
501     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
502                  floating_ip.ip)
503     fips = neutron.list_floatingips(ip=floating_ip.id)
504
505     for fip in fips['floatingips']:
506         if fip['id'] == floating_ip.id:
507             return fip
508
509
510 def delete_floating_ip(neutron, floating_ip):
511     """
512     Responsible for deleting a floating IP
513     :param neutron: the Neutron client
514     :param floating_ip: the SNAPS FloatingIp object
515     :return:
516     """
517     logger.debug('Attempting to delete existing floating ip with IP - %s',
518                  floating_ip.ip)
519     return neutron.delete_floatingip(floating_ip.id)