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