87836905b1fd07d1c21c550ca968fc77859f1556
[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.project import NetworkQuotas
24 from snaps.domain.vm_inst import FloatingIp
25 from snaps.openstack.utils import keystone_utils
26
27 __author__ = 'spisarski'
28
29 logger = logging.getLogger('neutron_utils')
30
31 """
32 Utilities for basic neutron API calls
33 """
34
35
36 def neutron_client(os_creds, session=None):
37     """
38     Instantiates and returns a client for communications with OpenStack's
39     Neutron server
40     :param os_creds: the credentials for connecting to the OpenStack remote API
41     :param session: the keystone session object (optional)
42     :return: the client object
43     """
44     if not session:
45         session = keystone_utils.keystone_session(os_creds)
46     return Client(api_version=os_creds.network_api_version,
47                   session=session,
48                   region_name=os_creds.region_name)
49
50
51 def create_network(neutron, os_creds, network_settings):
52     """
53     Creates a network for OpenStack
54     :param neutron: the client
55     :param os_creds: the OpenStack credentials
56     :param network_settings: A dictionary containing the network configuration
57                              and is responsible for creating the network
58                             request JSON body
59     :return: a SNAPS-OO Network domain object if found else None
60     """
61     logger.info('Creating network with name ' + network_settings.name)
62     json_body = network_settings.dict_for_neutron(os_creds)
63     os_network = neutron.create_network(body=json_body)
64
65     if os_network:
66         network = get_network_by_id(neutron, os_network['network']['id'])
67
68         subnets = list()
69         for subnet_settings in network_settings.subnet_settings:
70             try:
71                 subnets.append(
72                     create_subnet(neutron, subnet_settings, os_creds, network))
73             except:
74                 logger.error(
75                     'Unexpected error creating subnet [%s]  for network [%s]',
76                     subnet_settings.name, network.name)
77
78                 for subnet in subnets:
79                     delete_subnet(neutron, subnet)
80
81                 delete_network(neutron, network)
82
83                 raise
84
85         return get_network_by_id(neutron, network.id)
86
87
88 def delete_network(neutron, network):
89     """
90     Deletes a network for OpenStack
91     :param neutron: the client
92     :param network: a SNAPS-OO Network domain object
93     """
94     if neutron and network:
95         if network.subnets:
96             for subnet in network.subnets:
97                 logger.info('Deleting subnet with name ' + subnet.name)
98                 try:
99                     delete_subnet(neutron, subnet)
100                 except NotFound:
101                     pass
102
103         logger.info('Deleting network with name ' + network.name)
104         neutron.delete_network(network.id)
105
106
107 def get_network(neutron, keystone, network_settings=None, network_name=None,
108                 project_name=None):
109     """
110     Returns Network SNAPS-OO domain object the first network found with
111     either the given attributes from the network_settings object if not None,
112     else the query will use just the name from the network_name parameter.
113     When the project_name is included, that will be added to the query filter.
114     :param neutron: the Neutron client
115     :param keystone: the Keystone client
116     :param network_settings: the NetworkConfig object used to create filter
117     :param network_name: the name of the network to retrieve
118     :param project_name: the name of the network's project
119     :return: a SNAPS-OO Network domain object
120     """
121     net_filter = dict()
122     if network_settings:
123         net_filter['name'] = network_settings.name
124     elif network_name:
125         net_filter['name'] = network_name
126
127     networks = neutron.list_networks(**net_filter)
128     for network, netInsts in networks.items():
129         for inst in netInsts:
130             if project_name:
131                 if 'project_id' in inst.keys():
132                     project = keystone_utils.get_project_by_id(
133                         keystone, inst['project_id'])
134                 else:
135                     project = keystone_utils.get_project_by_id(
136                         keystone, inst['tenant_id'])
137                 if project and project.name == project_name:
138                     return __map_network(neutron, inst)
139             else:
140                 return __map_network(neutron, inst)
141
142
143 def __get_os_network_by_id(neutron, network_id):
144     """
145     Returns the OpenStack network object (dictionary) with the given ID else
146     None
147     :param neutron: the client
148     :param network_id: the id of the network to retrieve
149     :return: a SNAPS-OO Network domain object
150     """
151     networks = neutron.list_networks(**{'id': network_id})
152     for network in networks['networks']:
153         if network['id'] == network_id:
154             return network
155
156
157 def get_network_by_id(neutron, network_id):
158     """
159     Returns the SNAPS Network domain object for the given ID else None
160     :param neutron: the client
161     :param network_id: the id of the network to retrieve
162     :return: a SNAPS-OO Network domain object
163     """
164     os_network = __get_os_network_by_id(neutron, network_id)
165     if os_network:
166         return __map_network(neutron, os_network)
167
168
169 def __map_network(neutron, os_network):
170     """
171     Returns the network object (dictionary) with the given ID else None
172     :param neutron: the client
173     :param os_network: the OpenStack Network dict
174     :return: a SNAPS-OO Network domain object
175     """
176     subnets = get_subnets_by_network_id(neutron, os_network['id'])
177     os_network['subnets'] = subnets
178     return Network(**os_network)
179
180
181 def create_subnet(neutron, subnet_settings, os_creds, network):
182     """
183     Creates a network subnet for OpenStack
184     :param neutron: the client
185     :param subnet_settings: A dictionary containing the subnet configuration
186                             and is responsible for creating the subnet request
187                             JSON body
188     :param os_creds: the OpenStack credentials
189     :param network: the network object
190     :return: a SNAPS-OO Subnet domain object
191     """
192     if neutron and network and subnet_settings:
193         json_body = {'subnets': [subnet_settings.dict_for_neutron(
194             os_creds, network=network)]}
195         logger.info('Creating subnet with name ' + subnet_settings.name)
196         subnets = neutron.create_subnet(body=json_body)
197         return Subnet(**subnets['subnets'][0])
198     else:
199         raise NeutronException('Failed to create subnet')
200
201
202 def delete_subnet(neutron, subnet):
203     """
204     Deletes a network subnet for OpenStack
205     :param neutron: the client
206     :param subnet: a SNAPS-OO Subnet domain object
207     """
208     if neutron and subnet:
209         logger.info('Deleting subnet with name ' + subnet.name)
210         neutron.delete_subnet(subnet.id)
211
212
213 def get_subnet(neutron, network, subnet_settings=None, subnet_name=None):
214     """
215     Returns the first subnet object that fits the query else None including
216     if subnet_settings or subnet_name parameters are None.
217     :param neutron: the client
218     :param network: the associated SNAPS-OO Network domain object
219     :param subnet_settings: the subnet settings of the object to retrieve
220     :param subnet_name: the name of the subnet to retrieve
221     :return: a SNAPS-OO Subnet domain object or None
222     """
223     sub_filter = {'network_id': network.id}
224     if subnet_settings:
225         sub_filter['name'] = subnet_settings.name
226         sub_filter['cidr'] = subnet_settings.cidr
227         if subnet_settings.gateway_ip:
228             sub_filter['gateway_ip'] = subnet_settings.gateway_ip
229
230         if subnet_settings.enable_dhcp is not None:
231             sub_filter['enable_dhcp'] = subnet_settings.enable_dhcp
232
233         if subnet_settings.destination:
234             sub_filter['destination'] = subnet_settings.destination
235
236         if subnet_settings.nexthop:
237             sub_filter['nexthop'] = subnet_settings.nexthop
238
239         if subnet_settings.ipv6_ra_mode:
240             sub_filter['ipv6_ra_mode'] = subnet_settings.ipv6_ra_mode
241
242         if subnet_settings.ipv6_address_mode:
243             sub_filter['ipv6_address_mode'] = subnet_settings.ipv6_address_mode
244     elif subnet_name:
245         sub_filter['name'] = subnet_name
246     else:
247         return None
248
249     subnets = neutron.list_subnets(**sub_filter)
250     for subnet in subnets['subnets']:
251         return Subnet(**subnet)
252
253
254 def get_subnet_by_name(neutron, keystone, subnet_name, project_name=None):
255     """
256     Returns the first subnet object that fits the query else None including
257     if subnet_settings or subnet_name parameters are None.
258     :param neutron: the Neutron client
259     :param keystone: the Keystone client
260     :param subnet_name: the name of the subnet to retrieve
261     :param project_name: the name of the associated project to the subnet to
262                          retrieve
263     :return: a SNAPS-OO Subnet domain object or None
264     """
265     project = None
266     if project_name:
267         project = keystone_utils.get_project(
268             keystone, project_name=project_name)
269     if project:
270         sub_filter = {'name': subnet_name, 'project_id': project.id}
271         subnets = neutron.list_subnets(**sub_filter)
272         for subnet in subnets['subnets']:
273             return Subnet(**subnet)
274     else:
275         sub_filter = {'name': subnet_name}
276         subnets = neutron.list_subnets(**sub_filter)
277         for subnet in subnets['subnets']:
278             return Subnet(**subnet)
279
280
281 def get_subnet_by_id(neutron, subnet_id):
282     """
283     Returns a SNAPS-OO Subnet domain object for a given ID
284     :param neutron: the OpenStack neutron client
285     :param subnet_id: the subnet ID
286     :return: a Subnet object
287     """
288     os_subnet = neutron.show_subnet(subnet_id)
289     if os_subnet and 'subnet' in os_subnet:
290         return Subnet(**os_subnet['subnet'])
291
292
293 def get_subnets_by_network(neutron, network):
294     """
295     Returns a list of SNAPS-OO Subnet domain objects
296     :param neutron: the OpenStack neutron client
297     :param network: the SNAPS-OO Network domain object
298     :return: a list of Subnet objects
299     """
300     return get_subnets_by_network_id(neutron, network.id)
301
302
303 def get_subnets_by_network_id(neutron, network_id):
304     """
305     Returns a list of SNAPS-OO Subnet domain objects
306     :param neutron: the OpenStack neutron client
307     :param network_id: the subnet's ID
308     :return: a list of Subnet objects
309     """
310     out = list()
311
312     os_subnets = neutron.list_subnets(network_id=network_id)
313
314     for os_subnet in os_subnets['subnets']:
315         out.append(Subnet(**os_subnet))
316
317     return out
318
319
320 def create_router(neutron, os_creds, router_settings):
321     """
322     Creates a router for OpenStack
323     :param neutron: the client
324     :param os_creds: the OpenStack credentials
325     :param router_settings: A dictionary containing the router configuration
326                             and is responsible for creating the subnet request
327                             JSON body
328     :return: a SNAPS-OO Router domain object
329     """
330     if neutron:
331         json_body = router_settings.dict_for_neutron(neutron, os_creds)
332         logger.info('Creating router with name - ' + router_settings.name)
333         os_router = neutron.create_router(json_body)
334         return __map_router(neutron, os_router['router'])
335     else:
336         logger.error("Failed to create router.")
337         raise NeutronException('Failed to create router')
338
339
340 def delete_router(neutron, router):
341     """
342     Deletes a router for OpenStack
343     :param neutron: the client
344     :param router: a SNAPS-OO Router domain object
345     """
346     if neutron and router:
347         logger.info('Deleting router with name - ' + router.name)
348         neutron.delete_router(router=router.id)
349
350
351 def get_router_by_id(neutron, router_id):
352     """
353     Returns a router with a given ID, else None if not found
354     :param neutron: the client
355     :param router_id: the Router ID
356     :return: a SNAPS-OO Router domain object
357     """
358     router = neutron.show_router(router_id)
359     if router:
360         return __map_router(neutron, router['router'])
361
362
363 def get_router(neutron, keystone, router_settings=None, router_name=None,
364                project_name=None):
365     """
366     Returns the first router object (dictionary) found the given the settings
367     values if not None, else finds the first with the value of the router_name
368     parameter, else None
369     :param neutron: the Neutron client
370     :param keystone: the Keystone client
371     :param router_settings: the RouterConfig object
372     :param router_name: the name of the network to retrieve
373     :param project_name: the name of the router's project
374     :return: a SNAPS-OO Router domain object
375     """
376     router_filter = dict()
377     if router_settings:
378         router_filter['name'] = router_settings.name
379         if router_settings.admin_state_up is not None:
380             router_filter['admin_state_up'] = router_settings.admin_state_up
381     elif router_name:
382         router_filter['name'] = router_name
383     else:
384         return None
385
386     os_routers = neutron.list_routers(**router_filter)
387     for os_router in os_routers['routers']:
388         if project_name:
389             if 'project_id' in os_router.keys():
390                 project = keystone_utils.get_project_by_id(
391                     keystone, os_router['project_id'])
392             else:
393                 project = keystone_utils.get_project_by_id(
394                     keystone, os_router['tenant_id'])
395             if project and project.name == project_name:
396                 return __map_router(neutron, os_router)
397
398
399 def __map_router(neutron, os_router):
400     """
401     Takes an OpenStack router instance and maps it to a SNAPS Router domain
402     object
403     :param neutron: the neutron client
404     :param os_router: the OpenStack Router object
405     :return:
406     """
407     device_ports = neutron.list_ports(
408         **{'device_id': os_router['id']})['ports']
409     port_subnets = list()
410
411     # Order by create date
412     sorted_ports = sorted(
413         device_ports, key=lambda dev_port: dev_port['created_at'])
414
415     for port in sorted_ports:
416         subnets = list()
417         for fixed_ip in port['fixed_ips']:
418             subnet = get_subnet_by_id(neutron, fixed_ip['subnet_id'])
419             if subnet and subnet.network_id == port['network_id']:
420                 subnets.append(subnet)
421         port_subnets.append((Port(**port), subnets))
422
423     os_router['port_subnets'] = port_subnets
424     return Router(**os_router)
425
426
427 def add_interface_router(neutron, router, subnet=None, port=None):
428     """
429     Adds an interface router for OpenStack for either a subnet or port.
430     Exception will be raised if requesting for both.
431     :param neutron: the client
432     :param router: the router object
433     :param subnet: the subnet object
434     :param port: the port object
435     :return: the interface router object
436     """
437     if subnet and port:
438         raise NeutronException(
439             'Cannot add interface to the router. Both subnet and '
440             'port were sent in. Either or please.')
441
442     if neutron and router and (router or subnet):
443         logger.info('Adding interface to router with name ' + router.name)
444         os_intf_router = neutron.add_interface_router(
445             router=router.id, body=__create_port_json_body(subnet, port))
446         return InterfaceRouter(**os_intf_router)
447     else:
448         raise NeutronException(
449             'Unable to create interface router as neutron client,'
450             ' router or subnet were not created')
451
452
453 def remove_interface_router(neutron, router, subnet=None, port=None):
454     """
455     Removes an interface router for OpenStack
456     :param neutron: the client
457     :param router: the SNAPS-OO Router domain object
458     :param subnet: the subnet object (either subnet or port, not both)
459     :param port: the port object
460     """
461     if router:
462         try:
463             logger.info('Removing router interface from router named ' +
464                         router.name)
465             neutron.remove_interface_router(
466                 router=router.id,
467                 body=__create_port_json_body(subnet, port))
468         except NotFound as e:
469             logger.warning('Could not remove router interface. NotFound - %s',
470                            e)
471             pass
472     else:
473         logger.warning('Could not remove router interface, No router object')
474
475
476 def __create_port_json_body(subnet=None, port=None):
477     """
478     Returns the dictionary required for creating and deleting router
479     interfaces. Will only work on a subnet or port object. Will throw and
480     exception if parameters contain both or neither
481     :param subnet: the subnet object
482     :param port: the port object
483     :return: the dict
484     """
485     if subnet and port:
486         raise NeutronException(
487             'Cannot create JSON body with both subnet and port')
488     if not subnet and not port:
489         raise NeutronException(
490             'Cannot create JSON body without subnet or port')
491
492     if subnet:
493         return {"subnet_id": subnet.id}
494     else:
495         return {"port_id": port.id}
496
497
498 def create_port(neutron, os_creds, port_settings):
499     """
500     Creates a port for OpenStack
501     :param neutron: the client
502     :param os_creds: the OpenStack credentials
503     :param port_settings: the settings object for port configuration
504     :return: the SNAPS-OO Port domain object
505     """
506     json_body = port_settings.dict_for_neutron(neutron, os_creds)
507     logger.info('Creating port for network with name - %s',
508                 port_settings.network_name)
509     os_port = neutron.create_port(body=json_body)['port']
510     return Port(**os_port)
511
512
513 def delete_port(neutron, port):
514     """
515     Removes an OpenStack port
516     :param neutron: the client
517     :param port: the SNAPS-OO Port domain object
518     """
519     logger.info('Deleting port with name ' + port.name)
520     neutron.delete_port(port.id)
521
522
523 def get_port(neutron, keystone, port_settings=None, port_name=None,
524              project_name=None):
525     """
526     Returns the first port object (dictionary) found for the given query
527     :param neutron: the Neutron client
528     :param keystone: the Keystone client
529     :param port_settings: the PortConfig object used for generating the query
530     :param port_name: if port_settings is None, this name is the value to place
531                       into the query
532     :param project_name: the associated project name
533     :return: a SNAPS-OO Port domain object
534     """
535     port_filter = dict()
536
537     if port_settings:
538         if port_settings.name and len(port_settings.name) > 0:
539             port_filter['name'] = port_settings.name
540         if port_settings.admin_state_up:
541             port_filter['admin_state_up'] = port_settings.admin_state_up
542         if port_settings.device_id:
543             port_filter['device_id'] = port_settings.device_id
544         if port_settings.mac_address:
545             port_filter['mac_address'] = port_settings.mac_address
546         if port_settings.project_name:
547             project_name = port_settings.project_name
548         if port_settings.network_name:
549             network = get_network(
550                 neutron, keystone, network_name=port_settings.network_name,
551                 project_name=project_name)
552             if network:
553                 port_filter['network_id'] = network.id
554     elif port_name:
555         port_filter['name'] = port_name
556
557     ports = neutron.list_ports(**port_filter)
558     for port in ports['ports']:
559         if project_name:
560             if 'project_id' in port.keys():
561                 project = keystone_utils.get_project_by_id(
562                     keystone, port['project_id'])
563             else:
564                 project = keystone_utils.get_project_by_id(
565                     keystone, port['tenant_id'])
566             if project and project.name == project_name:
567                 return Port(**port)
568         else:
569             return Port(**port)
570     return None
571
572
573 def get_port_by_id(neutron, port_id):
574     """
575     Returns a SNAPS-OO Port domain object for the given ID or none if not found
576     :param neutron: the client
577     :param port_id: the to query
578     :return: a SNAPS-OO Port domain object or None
579     """
580     port = neutron.show_port(port_id)
581     if port:
582         return Port(**port['port'])
583     return None
584
585
586 def get_ports(neutron, network, ips=None):
587     """
588     Returns a list of SNAPS-OO Port objects for all OpenStack Port objects that
589     are associated with the 'network' parameter
590     :param neutron: the client
591     :param network: SNAPS-OO Network domain object
592     :param ips: the IPs to lookup if not None
593     :return: a SNAPS-OO Port domain object or None if not found
594     """
595     out = list()
596     ports = neutron.list_ports(**{'network_id': network.id})
597     for port in ports['ports']:
598         if ips:
599             for fixed_ips in port['fixed_ips']:
600                 if ('ip_address' in fixed_ips and
601                         fixed_ips['ip_address'] in ips) or ips is None:
602                     out.append(Port(**port))
603                     break
604         else:
605             out.append(Port(**port))
606
607     return out
608
609
610 def create_security_group(neutron, keystone, sec_grp_settings):
611     """
612     Creates a security group object in OpenStack
613     :param neutron: the Neutron client
614     :param keystone: the Keystone client
615     :param sec_grp_settings: the security group settings
616     :return: a SNAPS-OO SecurityGroup domain object
617     """
618     logger.info('Creating security group with name - %s',
619                 sec_grp_settings.name)
620     os_group = neutron.create_security_group(
621         sec_grp_settings.dict_for_neutron(keystone))
622     return __map_os_security_group(neutron, os_group['security_group'])
623
624
625 def delete_security_group(neutron, sec_grp):
626     """
627     Deletes a security group object from OpenStack
628     :param neutron: the client
629     :param sec_grp: the SNAPS SecurityGroup object to delete
630     """
631     logger.info('Deleting security group with name - %s', sec_grp.name)
632     neutron.delete_security_group(sec_grp.id)
633
634
635 def get_security_group(neutron, keystone, sec_grp_settings=None,
636                        sec_grp_name=None, project_name=None):
637     """
638     Returns the first security group for a given query. The query gets built
639     from the sec_grp_settings parameter if not None, else only the name of
640     the security group will be used, else if the query parameters are None then
641     None will be returned
642     :param neutron: the neutron client
643     :param keystone: the keystone client
644     :param sec_grp_settings: an instance of SecurityGroupConfig object
645     :param sec_grp_name: the name of security group object to retrieve
646     :param project_name: the name of the project/tentant object that owns the
647                        secuity group to retrieve
648     :return: a SNAPS-OO SecurityGroup domain object or None if not found
649     """
650
651     sec_grp_filter = dict()
652
653     if sec_grp_settings:
654         sec_grp_filter['name'] = sec_grp_settings.name
655
656         if sec_grp_settings.description:
657             sec_grp_filter['description'] = sec_grp_settings.description
658         if sec_grp_settings.project_name:
659             project_name = sec_grp_settings.project_name
660     elif sec_grp_name:
661         sec_grp_filter['name'] = sec_grp_name
662     else:
663         return None
664
665     groups = neutron.list_security_groups(**sec_grp_filter)
666     for group in groups['security_groups']:
667         if project_name:
668             if 'project_id' in group.keys():
669                 project = keystone_utils.get_project_by_id(
670                     keystone, group['project_id'])
671             else:
672                 project = keystone_utils.get_project_by_id(
673                     keystone, group['tenant_id'])
674             if project and project_name == project.name:
675                 return __map_os_security_group(neutron, group)
676         else:
677             return __map_os_security_group(neutron, group)
678
679
680 def __map_os_security_group(neutron, os_sec_grp):
681     """
682     Creates a SecurityGroup SNAPS domain object from an OpenStack Security
683     Group dict
684     :param neutron: the neutron client for performing rule lookups
685     :param os_sec_grp: the OpenStack Security Group dict object
686     :return: a SecurityGroup object
687     """
688     os_sec_grp['rules'] = get_rules_by_security_group_id(
689         neutron, os_sec_grp['id'])
690     return SecurityGroup(**os_sec_grp)
691
692
693 def get_security_group_by_id(neutron, sec_grp_id):
694     """
695     Returns the first security group object of the given name else None
696     :param neutron: the client
697     :param sec_grp_id: the id of the security group to retrieve
698     :return: a SNAPS-OO SecurityGroup domain object or None if not found
699     """
700     logger.info('Retrieving security group with ID - ' + sec_grp_id)
701
702     groups = neutron.list_security_groups(**{'id': sec_grp_id})
703     for group in groups['security_groups']:
704         if group['id'] == sec_grp_id:
705             return __map_os_security_group(neutron, group)
706     return None
707
708
709 def list_security_groups(neutron):
710
711     """
712     Lists the available security groups
713     :param neutron: the neutron client
714     """
715     logger.info('Listing the available security groups')
716     sec_groups = []
717     response = neutron.list_security_groups()
718     for sg in response['security_groups']:
719         sec_groups.append(__map_os_security_group(neutron, sg))
720
721     return sec_groups
722
723
724 def create_security_group_rule(neutron, keystone, sec_grp_rule_settings,
725                                proj_name):
726     """
727     Creates a security group rule in OpenStack
728     :param neutron: the neutron client
729     :param keystone: the keystone client
730     :param sec_grp_rule_settings: the security group rule settings
731     :param proj_name: the default project name
732     :return: a SNAPS-OO SecurityGroupRule domain object
733     """
734     logger.info('Creating security group to security group - %s',
735                 sec_grp_rule_settings.sec_grp_name)
736     os_rule = neutron.create_security_group_rule(
737         sec_grp_rule_settings.dict_for_neutron(neutron, keystone, proj_name))
738     return SecurityGroupRule(**os_rule['security_group_rule'])
739
740
741 def delete_security_group_rule(neutron, sec_grp_rule):
742     """
743     Deletes a security group rule object from OpenStack
744     :param neutron: the client
745     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
746     """
747     logger.info('Deleting security group rule with ID - %s',
748                 sec_grp_rule.id)
749     neutron.delete_security_group_rule(sec_grp_rule.id)
750
751
752 def get_rules_by_security_group(neutron, sec_grp):
753     """
754     Retrieves all of the rules for a given security group
755     :param neutron: the client
756     :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
757     """
758     return get_rules_by_security_group_id(neutron, sec_grp.id)
759
760
761 def get_rules_by_security_group_id(neutron, sec_grp_id):
762     """
763     Retrieves all of the rules for a given security group by it's ID
764     :param neutron: the client
765     :param sec_grp_id: the ID of the associated security group
766     """
767     logger.info('Retrieving security group rules associate with the '
768                 'security group with ID - %s', sec_grp_id)
769     out = list()
770     rules = neutron.list_security_group_rules(
771         **{'security_group_id': sec_grp_id})
772     for rule in rules['security_group_rules']:
773         if rule['security_group_id'] == sec_grp_id:
774             out.append(SecurityGroupRule(**rule))
775     return out
776
777
778 def get_rule_by_id(neutron, sec_grp, rule_id):
779     """
780     Returns a SecurityGroupRule object from OpenStack
781     :param neutron: the client
782     :param sec_grp: the SNAPS SecurityGroup domain object
783     :param rule_id: the rule's ID
784     :param sec_grp: a SNAPS SecurityGroupRule domain object
785     """
786     rules = neutron.list_security_group_rules(
787         **{'security_group_id': sec_grp.id})
788     for rule in rules['security_group_rules']:
789         if rule['id'] == rule_id:
790             return SecurityGroupRule(**rule)
791     return None
792
793
794 def get_external_networks(neutron):
795     """
796     Returns a list of external OpenStack network object/dict for all external
797     networks
798     :param neutron: the client
799     :return: a list of external networks of Type SNAPS-OO domain class Network
800     """
801     out = list()
802     for network in neutron.list_networks(
803             **{'router:external': True})['networks']:
804         out.append(__map_network(neutron, network))
805     return out
806
807
808 def get_port_floating_ips(neutron, ports):
809     """
810     Returns all of the floating IPs associated with the ports returned in a
811     list of tuples where the port object is in the first position and the
812     floating IP object is in the second
813     :param neutron: the Neutron client
814     :param ports: a list of tuple 2 where index 0 is the port name and index 1
815                   is the SNAPS-OO Port object
816     :return: a list of tuple 2 (port_id, SNAPS FloatingIp) objects when ports
817              is not None else a list of FloatingIp objects
818     """
819     out = list()
820     fips = neutron.list_floatingips()
821     for fip in fips['floatingips']:
822         for port_name, port in ports:
823             if port and port.id == fip['port_id']:
824                 out.append((port.id, FloatingIp(**fip)))
825                 break
826     return out
827
828
829 def get_floating_ips(neutron):
830     """
831     Returns a list of all of the floating IPs
832     :param neutron: the Neutron client
833     """
834     out = list()
835     fips = neutron.list_floatingips()
836     for fip in fips['floatingips']:
837         out.append(FloatingIp(**fip))
838     return out
839
840
841 def create_floating_ip(neutron, keystone, ext_net_name, port_id=None):
842     """
843     Returns the floating IP object that was created with this call
844     :param neutron: the Neutron client
845     :param keystone: the Keystone client
846     :param ext_net_name: the name of the external network on which to apply the
847                          floating IP address
848     :param port_id: the ID of the port to which the floating IP will be
849                     associated
850     :return: the SNAPS FloatingIp object
851     """
852     logger.info('Creating floating ip to external network - ' + ext_net_name)
853     ext_net = get_network(neutron, keystone, network_name=ext_net_name)
854     if ext_net:
855         body = {'floatingip': {'floating_network_id': ext_net.id}}
856         if port_id:
857             body['floatingip']['port_id'] = port_id
858
859         fip = neutron.create_floatingip(body=body)
860
861         return FloatingIp(id=fip['floatingip']['id'],
862                           ip=fip['floatingip']['floating_ip_address'])
863     else:
864         raise NeutronException(
865             'Cannot create floating IP, external network not found')
866
867
868 def get_floating_ip(neutron, floating_ip):
869     """
870     Returns a floating IP object that should be identical to the floating_ip
871     parameter
872     :param neutron: the Neutron client
873     :param floating_ip: the SNAPS FloatingIp object
874     :return: hopefully the same floating IP object input
875     """
876     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
877                  floating_ip.ip)
878     os_fip = __get_os_floating_ip(neutron, floating_ip)
879     if os_fip:
880         return FloatingIp(id=os_fip['id'], ip=os_fip['floating_ip_address'])
881
882
883 def __get_os_floating_ip(neutron, floating_ip):
884     """
885     Returns an OpenStack floating IP object
886     parameter
887     :param neutron: the Neutron client
888     :param floating_ip: the SNAPS FloatingIp object
889     :return: hopefully the same floating IP object input
890     """
891     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
892                  floating_ip.ip)
893     fips = neutron.list_floatingips(ip=floating_ip.id)
894
895     for fip in fips['floatingips']:
896         if fip['id'] == floating_ip.id:
897             return fip
898
899
900 def delete_floating_ip(neutron, floating_ip):
901     """
902     Responsible for deleting a floating IP
903     :param neutron: the Neutron client
904     :param floating_ip: the SNAPS FloatingIp object
905     :return:
906     """
907     logger.debug('Attempting to delete existing floating ip with IP - %s',
908                  floating_ip.ip)
909     return neutron.delete_floatingip(floating_ip.id)
910
911
912 def get_network_quotas(neutron, project_id):
913     """
914     Returns a list of NetworkQuotas objects
915     :param neutron: the neutron client
916     :param project_id: the project's ID of the quotas to lookup
917     :return: an object of type NetworkQuotas or None if not found
918     """
919     quota = neutron.show_quota(project_id)
920     if quota:
921         return NetworkQuotas(**quota['quota'])
922
923
924 def update_quotas(neutron, project_id, network_quotas):
925     """
926     Updates the networking quotas for a given project
927     :param neutron: the Neutron client
928     :param project_id: the project's ID that requires quota updates
929     :param network_quotas: an object of type NetworkQuotas containing the
930                            values to update
931     :return:
932     """
933     update_body = dict()
934     update_body['security_group'] = network_quotas.security_group
935     update_body['security_group_rule'] = network_quotas.security_group_rule
936     update_body['floatingip'] = network_quotas.floatingip
937     update_body['network'] = network_quotas.network
938     update_body['port'] = network_quotas.port
939     update_body['router'] = network_quotas.router
940     update_body['subnet'] = network_quotas.subnet
941
942     return neutron.update_quota(project_id, {'quota': update_body})
943
944
945 class NeutronException(Exception):
946     """
947     Exception when calls to the Keystone client cannot be served properly
948     """