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