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