Added method to return OpenStackVmInstance from Heat.
[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):
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     :return: the client object
42     """
43     return Client(api_version=os_creds.network_api_version,
44                   session=keystone_utils.keystone_session(os_creds),
45                   region_name=os_creds.region_name)
46
47
48 def create_network(neutron, os_creds, network_settings):
49     """
50     Creates a network for OpenStack
51     :param neutron: the client
52     :param os_creds: the OpenStack credentials
53     :param network_settings: A dictionary containing the network configuration
54                              and is responsible for creating the network
55                             request JSON body
56     :return: a SNAPS-OO Network domain object
57     """
58     if neutron and network_settings:
59         logger.info('Creating network with name ' + network_settings.name)
60         json_body = network_settings.dict_for_neutron(os_creds)
61         os_network = neutron.create_network(body=json_body)
62         return Network(**os_network['network'])
63     else:
64         raise NeutronException('Failded to create network')
65
66
67 def delete_network(neutron, network):
68     """
69     Deletes a network for OpenStack
70     :param neutron: the client
71     :param network: a SNAPS-OO Network domain object
72     """
73     if neutron and network:
74         logger.info('Deleting network with name ' + network.name)
75         neutron.delete_network(network.id)
76
77
78 def get_network(neutron, network_settings=None, network_name=None,
79                 project_id=None):
80     """
81     Returns Network SNAPS-OO domain object the first network found with
82     either the given attributes from the network_settings object if not None,
83     else the query will use just the name from the network_name parameter.
84     When the project_id is included, that will be added to the query filter.
85     :param neutron: the client
86     :param network_settings: the NetworkSettings object used to create filter
87     :param network_name: the name of the network to retrieve
88     :param project_id: the id of the network's project
89     :return: a SNAPS-OO Network domain object
90     """
91     net_filter = dict()
92     if network_settings:
93         net_filter['name'] = network_settings.name
94     elif network_name:
95         net_filter['name'] = network_name
96
97     if project_id:
98         net_filter['project_id'] = project_id
99
100     networks = neutron.list_networks(**net_filter)
101     for network, netInsts in networks.items():
102         for inst in netInsts:
103             return Network(**inst)
104
105
106 def get_network_by_id(neutron, network_id):
107     """
108     Returns the network object (dictionary) with the given ID else None
109     :param neutron: the client
110     :param network_id: the id of the network to retrieve
111     :return: a SNAPS-OO Network domain object
112     """
113     networks = neutron.list_networks(**{'id': network_id})
114     for network in networks['networks']:
115         if network['id'] == network_id:
116             return Network(**network)
117
118
119 def create_subnet(neutron, subnet_settings, os_creds, network=None):
120     """
121     Creates a network subnet for OpenStack
122     :param neutron: the client
123     :param network: the network object
124     :param subnet_settings: A dictionary containing the subnet configuration
125                             and is responsible for creating the subnet request
126                             JSON body
127     :param os_creds: the OpenStack credentials
128     :return: a SNAPS-OO Subnet domain object
129     """
130     if neutron and network and subnet_settings:
131         json_body = {'subnets': [subnet_settings.dict_for_neutron(
132             os_creds, network=network)]}
133         logger.info('Creating subnet with name ' + subnet_settings.name)
134         subnets = neutron.create_subnet(body=json_body)
135         return Subnet(**subnets['subnets'][0])
136     else:
137         raise NeutronException('Failed to create subnet')
138
139
140 def delete_subnet(neutron, subnet):
141     """
142     Deletes a network subnet for OpenStack
143     :param neutron: the client
144     :param subnet: a SNAPS-OO Subnet domain object
145     """
146     if neutron and subnet:
147         logger.info('Deleting subnet with name ' + subnet.name)
148         neutron.delete_subnet(subnet.id)
149
150
151 def get_subnet(neutron, subnet_settings=None, subnet_name=None):
152     """
153     Returns the first subnet object that fits the query else None including
154     if subnet_settings or subnet_name parameters are None.
155     :param neutron: the client
156     :param subnet_settings: the subnet settings of the object to retrieve
157     :param subnet_name: the name of the subnet to retrieve
158     :return: a SNAPS-OO Subnet domain object or None
159     """
160     sub_filter = dict()
161     if subnet_settings:
162         sub_filter['name'] = subnet_settings.name
163         sub_filter['cidr'] = subnet_settings.cidr
164         if subnet_settings.gateway_ip:
165             sub_filter['gateway_ip'] = subnet_settings.gateway_ip
166
167         if subnet_settings.enable_dhcp is not None:
168             sub_filter['enable_dhcp'] = subnet_settings.enable_dhcp
169
170         if subnet_settings.destination:
171             sub_filter['destination'] = subnet_settings.destination
172
173         if subnet_settings.nexthop:
174             sub_filter['nexthop'] = subnet_settings.nexthop
175
176         if subnet_settings.ipv6_ra_mode:
177             sub_filter['ipv6_ra_mode'] = subnet_settings.ipv6_ra_mode
178
179         if subnet_settings.ipv6_address_mode:
180             sub_filter['ipv6_address_mode'] = subnet_settings.ipv6_address_mode
181     elif subnet_name:
182         sub_filter['name'] = subnet_name
183     else:
184         return None
185
186     subnets = neutron.list_subnets(**sub_filter)
187     for subnet in subnets['subnets']:
188         return Subnet(**subnet)
189
190
191 def get_subnet_by_id(neutron, subnet_id):
192     """
193     Returns a SNAPS-OO Subnet domain object for a given ID
194     :param neutron: the OpenStack neutron client
195     :param subnet_id: the subnet ID
196     :return: a Subnet object
197     """
198     os_subnet = neutron.show_subnet(subnet_id)
199     if os_subnet and 'subnet' in os_subnet:
200         return Subnet(**os_subnet['subnet'])
201
202
203 def get_subnets_by_network(neutron, network):
204     """
205     Returns a list of SNAPS-OO Subnet domain objects
206     :param neutron: the OpenStack neutron client
207     :param network: the SNAPS-OO Network domain object
208     :return: a list of Subnet objects
209     """
210     out = list()
211
212     os_subnets = neutron.list_subnets(network_id=network.id)
213
214     for os_subnet in os_subnets['subnets']:
215         out.append(Subnet(**os_subnet))
216
217     return out
218
219
220 def create_router(neutron, os_creds, router_settings):
221     """
222     Creates a router for OpenStack
223     :param neutron: the client
224     :param os_creds: the OpenStack credentials
225     :param router_settings: A dictionary containing the router configuration
226                             and is responsible for creating the subnet request
227                             JSON body
228     :return: a SNAPS-OO Router domain object
229     """
230     if neutron:
231         json_body = router_settings.dict_for_neutron(neutron, os_creds)
232         logger.info('Creating router with name - ' + router_settings.name)
233         os_router = neutron.create_router(json_body)
234         return Router(**os_router['router'])
235     else:
236         logger.error("Failed to create router.")
237         raise NeutronException('Failed to create router')
238
239
240 def delete_router(neutron, router):
241     """
242     Deletes a router for OpenStack
243     :param neutron: the client
244     :param router: a SNAPS-OO Router domain object
245     """
246     if neutron and router:
247         logger.info('Deleting router with name - ' + router.name)
248         neutron.delete_router(router=router.id)
249
250
251 def get_router_by_id(neutron, router_id):
252     """
253     Returns a router with a given ID, else None if not found
254     :param neutron: the client
255     :param router_id: the Router ID
256     :return: a SNAPS-OO Router domain object
257     """
258     router = neutron.show_router(router_id)
259     if router:
260         return Router(**router['router'])
261
262
263 def get_router(neutron, router_settings=None, router_name=None):
264     """
265     Returns the first router object (dictionary) found the given the settings
266     values if not None, else finds the first with the value of the router_name
267     parameter, else None
268     :param neutron: the client
269     :param router_settings: the RouterSettings object
270     :param router_name: the name of the network to retrieve
271     :return: a SNAPS-OO Router domain object
272     """
273     router_filter = dict()
274     if router_settings:
275         router_filter['name'] = router_settings.name
276         if router_settings.admin_state_up is not None:
277             router_filter['admin_state_up'] = router_settings.admin_state_up
278     elif router_name:
279         router_filter['name'] = router_name
280     else:
281         return None
282
283     routers = neutron.list_routers(**router_filter)
284     for routerInst in routers['routers']:
285         return Router(**routerInst)
286     return None
287
288
289 def add_interface_router(neutron, router, subnet=None, port=None):
290     """
291     Adds an interface router for OpenStack for either a subnet or port.
292     Exception will be raised if requesting for both.
293     :param neutron: the client
294     :param router: the router object
295     :param subnet: the subnet object
296     :param port: the port object
297     :return: the interface router object
298     """
299     if subnet and port:
300         raise NeutronException(
301             'Cannot add interface to the router. Both subnet and '
302             'port were sent in. Either or please.')
303
304     if neutron and router and (router or subnet):
305         logger.info('Adding interface to router with name ' + router.name)
306         os_intf_router = neutron.add_interface_router(
307             router=router.id, body=__create_port_json_body(subnet, port))
308         return InterfaceRouter(**os_intf_router)
309     else:
310         raise NeutronException(
311             'Unable to create interface router as neutron client,'
312             ' router or subnet were not created')
313
314
315 def remove_interface_router(neutron, router, subnet=None, port=None):
316     """
317     Removes an interface router for OpenStack
318     :param neutron: the client
319     :param router: the SNAPS-OO Router domain object
320     :param subnet: the subnet object (either subnet or port, not both)
321     :param port: the port object
322     """
323     if router:
324         try:
325             logger.info('Removing router interface from router named ' +
326                         router.name)
327             neutron.remove_interface_router(
328                 router=router.id,
329                 body=__create_port_json_body(subnet, port))
330         except NotFound as e:
331             logger.warning('Could not remove router interface. NotFound - %s',
332                            e)
333             pass
334     else:
335         logger.warning('Could not remove router interface, No router object')
336
337
338 def __create_port_json_body(subnet=None, port=None):
339     """
340     Returns the dictionary required for creating and deleting router
341     interfaces. Will only work on a subnet or port object. Will throw and
342     exception if parameters contain both or neither
343     :param subnet: the subnet object
344     :param port: the port object
345     :return: the dict
346     """
347     if subnet and port:
348         raise NeutronException(
349             'Cannot create JSON body with both subnet and port')
350     if not subnet and not port:
351         raise NeutronException(
352             'Cannot create JSON body without subnet or port')
353
354     if subnet:
355         return {"subnet_id": subnet.id}
356     else:
357         return {"port_id": port.id}
358
359
360 def create_port(neutron, os_creds, port_settings):
361     """
362     Creates a port for OpenStack
363     :param neutron: the client
364     :param os_creds: the OpenStack credentials
365     :param port_settings: the settings object for port configuration
366     :return: the SNAPS-OO Port domain object
367     """
368     json_body = port_settings.dict_for_neutron(neutron, os_creds)
369     logger.info('Creating port for network with name - %s',
370                 port_settings.network_name)
371     os_port = neutron.create_port(body=json_body)['port']
372     return Port(name=os_port['name'], id=os_port['id'],
373                 ips=os_port['fixed_ips'],
374                 mac_address=os_port['mac_address'],
375                 allowed_address_pairs=os_port['allowed_address_pairs'])
376
377
378 def delete_port(neutron, port):
379     """
380     Removes an OpenStack port
381     :param neutron: the client
382     :param port: the SNAPS-OO Port domain object
383     """
384     logger.info('Deleting port with name ' + port.name)
385     neutron.delete_port(port.id)
386
387
388 def get_port(neutron, port_settings=None, port_name=None):
389     """
390     Returns the first port object (dictionary) found for the given query
391     :param neutron: the client
392     :param port_settings: the PortSettings object used for generating the query
393     :param port_name: if port_settings is None, this name is the value to place
394                       into the query
395     :return: a SNAPS-OO Port domain object
396     """
397     port_filter = dict()
398
399     if port_settings:
400         if port_settings.name and len(port_settings.name) > 0:
401             port_filter['name'] = port_settings.name
402         if port_settings.admin_state_up:
403             port_filter['admin_state_up'] = port_settings.admin_state_up
404         if port_settings.device_id:
405             port_filter['device_id'] = port_settings.device_id
406         if port_settings.mac_address:
407             port_filter['mac_address'] = port_settings.mac_address
408         if port_settings.network_name:
409             network = get_network(neutron,
410                                   network_name=port_settings.network_name)
411             port_filter['network_id'] = network.id
412     elif port_name:
413         port_filter['name'] = port_name
414
415     ports = neutron.list_ports(**port_filter)
416     for port in ports['ports']:
417         return Port(**port)
418     return None
419
420
421 def get_port_by_id(neutron, port_id):
422     """
423     Returns a SNAPS-OO Port domain object for the given ID or none if not found
424     :param neutron: the client
425     :param port_id: the to query
426     :return: a SNAPS-OO Port domain object or None
427     """
428     port = neutron.show_port(port_id)
429     if port:
430         return Port(**port['port'])
431     return None
432
433
434 def get_ports(neutron, network, ips=None):
435     """
436     Returns a list of SNAPS-OO Port objects for all OpenStack Port objects that
437     are associated with the 'network' parameter
438     :param neutron: the client
439     :param network: SNAPS-OO Network domain object
440     :param ips: the IPs to lookup if not None
441     :return: a SNAPS-OO Port domain object or None if not found
442     """
443     out = list()
444     ports = neutron.list_ports(**{'network_id': network.id})
445     for port in ports['ports']:
446         if ips:
447             for fixed_ips in port['fixed_ips']:
448                 if ('ip_address' in fixed_ips and
449                         fixed_ips['ip_address'] in ips) or ips is None:
450                     out.append(Port(**port))
451                     break
452         else:
453             out.append(Port(**port))
454
455     return out
456
457
458 def create_security_group(neutron, keystone, sec_grp_settings):
459     """
460     Creates a security group object in OpenStack
461     :param neutron: the Neutron client
462     :param keystone: the Keystone client
463     :param sec_grp_settings: the security group settings
464     :return: a SNAPS-OO SecurityGroup domain object
465     """
466     logger.info('Creating security group with name - %s',
467                 sec_grp_settings.name)
468     os_group = neutron.create_security_group(
469         sec_grp_settings.dict_for_neutron(keystone))
470     return SecurityGroup(**os_group['security_group'])
471
472
473 def delete_security_group(neutron, sec_grp):
474     """
475     Deletes a security group object from OpenStack
476     :param neutron: the client
477     :param sec_grp: the SNAPS SecurityGroup object to delete
478     """
479     logger.info('Deleting security group with name - %s', sec_grp.name)
480     neutron.delete_security_group(sec_grp.id)
481
482
483 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
484                        project_id=None):
485     """
486     Returns the first security group for a given query. The query gets built
487     from the sec_grp_settings parameter if not None, else only the name of
488     the security group will be used, else if the query parameters are None then
489     None will be returned
490     :param neutron: the client
491     :param sec_grp_settings: an instance of SecurityGroupSettings config object
492     :param sec_grp_name: the name of security group object to retrieve
493     :param project_id: the ID of the project/tentant object that owns the
494                        secuity group to retrieve
495     :return: a SNAPS-OO SecurityGroup domain object or None if not found
496     """
497
498     sec_grp_filter = dict()
499     if project_id:
500         sec_grp_filter['tenant_id'] = project_id
501
502     if sec_grp_settings:
503         sec_grp_filter['name'] = sec_grp_settings.name
504
505         if sec_grp_settings.description:
506             sec_grp_filter['description'] = sec_grp_settings.description
507     elif sec_grp_name:
508         sec_grp_filter['name'] = sec_grp_name
509     else:
510         return None
511
512     groups = neutron.list_security_groups(**sec_grp_filter)
513     for group in groups['security_groups']:
514         return SecurityGroup(**group)
515
516
517 def get_security_group_by_id(neutron, sec_grp_id):
518     """
519     Returns the first security group object of the given name else None
520     :param neutron: the client
521     :param sec_grp_id: the id of the security group to retrieve
522     :return: a SNAPS-OO SecurityGroup domain object or None if not found
523     """
524     logger.info('Retrieving security group with ID - ' + sec_grp_id)
525
526     groups = neutron.list_security_groups(**{'id': sec_grp_id})
527     for group in groups['security_groups']:
528         if group['id'] == sec_grp_id:
529             return SecurityGroup(**group)
530     return None
531
532
533 def create_security_group_rule(neutron, sec_grp_rule_settings):
534     """
535     Creates a security group object in OpenStack
536     :param neutron: the client
537     :param sec_grp_rule_settings: the security group rule settings
538     :return: a SNAPS-OO SecurityGroupRule domain object
539     """
540     logger.info('Creating security group to security group - %s',
541                 sec_grp_rule_settings.sec_grp_name)
542     os_rule = neutron.create_security_group_rule(
543         sec_grp_rule_settings.dict_for_neutron(neutron))
544     return SecurityGroupRule(**os_rule['security_group_rule'])
545
546
547 def delete_security_group_rule(neutron, sec_grp_rule):
548     """
549     Deletes a security group object from OpenStack
550     :param neutron: the client
551     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
552     """
553     logger.info('Deleting security group rule with ID - %s',
554                 sec_grp_rule.id)
555     neutron.delete_security_group_rule(sec_grp_rule.id)
556
557
558 def get_rules_by_security_group(neutron, sec_grp):
559     """
560     Retrieves all of the rules for a given security group
561     :param neutron: the client
562     :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
563     """
564     logger.info('Retrieving security group rules associate with the '
565                 'security group - %s', sec_grp.name)
566     out = list()
567     rules = neutron.list_security_group_rules(
568         **{'security_group_id': sec_grp.id})
569     for rule in rules['security_group_rules']:
570         if rule['security_group_id'] == sec_grp.id:
571             out.append(SecurityGroupRule(**rule))
572     return out
573
574
575 def get_rule_by_id(neutron, sec_grp, rule_id):
576     """
577     Deletes a security group object from OpenStack
578     :param neutron: the client
579     :param sec_grp: the SNAPS SecurityGroup domain object
580     :param rule_id: the rule's ID
581     :param sec_grp: a SNAPS SecurityGroupRule domain object
582     """
583     rules = neutron.list_security_group_rules(
584         **{'security_group_id': sec_grp.id})
585     for rule in rules['security_group_rules']:
586         if rule['id'] == rule_id:
587             return SecurityGroupRule(**rule)
588     return None
589
590
591 def get_external_networks(neutron):
592     """
593     Returns a list of external OpenStack network object/dict for all external
594     networks
595     :param neutron: the client
596     :return: a list of external networks of Type SNAPS-OO domain class Network
597     """
598     out = list()
599     for network in neutron.list_networks(
600             **{'router:external': True})['networks']:
601         out.append(Network(**network))
602     return out
603
604
605 def get_floating_ips(neutron, ports=None):
606     """
607     Returns all of the floating IPs
608     When ports is not None, FIPs returned must be associated with one of the
609     ports in the list and a tuple 2 where the first element being the port's
610     ID and the second being the FloatingIp SNAPS-OO domain object.
611     When ports is None, all known FloatingIp SNAPS-OO domain objects will be
612     returned in a list
613     :param neutron: the Neutron client
614     :param ports: a list of tuple 2 where index 0 is the port name and index 1
615                   is the SNAPS-OO Port object
616     :return: a list of tuple 2 (port_id, SNAPS FloatingIp) objects when ports
617              is not None else a list of Port objects
618     """
619     out = list()
620     fips = neutron.list_floatingips()
621     for fip in fips['floatingips']:
622         if ports:
623             for port_name, port in ports:
624                 if port and port.id == fip['port_id']:
625                     out.append((port.id, FloatingIp(**fip)))
626                     break
627         else:
628             out.append(FloatingIp(**fip))
629
630     return out
631
632
633 def create_floating_ip(neutron, ext_net_name):
634     """
635     Returns the floating IP object that was created with this call
636     :param neutron: the Neutron client
637     :param ext_net_name: the name of the external network on which to apply the
638                          floating IP address
639     :return: the SNAPS FloatingIp object
640     """
641     logger.info('Creating floating ip to external network - ' + ext_net_name)
642     ext_net = get_network(neutron, network_name=ext_net_name)
643     if ext_net:
644         fip = neutron.create_floatingip(
645             body={'floatingip':
646                   {'floating_network_id': ext_net.id}})
647
648         return FloatingIp(id=fip['floatingip']['id'],
649                           ip=fip['floatingip']['floating_ip_address'])
650     else:
651         raise NeutronException(
652             'Cannot create floating IP, external network not found')
653
654
655 def get_floating_ip(neutron, floating_ip):
656     """
657     Returns a floating IP object that should be identical to the floating_ip
658     parameter
659     :param neutron: the Neutron client
660     :param floating_ip: the SNAPS FloatingIp object
661     :return: hopefully the same floating IP object input
662     """
663     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
664                  floating_ip.ip)
665     os_fip = __get_os_floating_ip(neutron, floating_ip)
666     if os_fip:
667         return FloatingIp(id=os_fip['id'], ip=os_fip['floating_ip_address'])
668
669
670 def __get_os_floating_ip(neutron, floating_ip):
671     """
672     Returns an OpenStack floating IP object
673     parameter
674     :param neutron: the Neutron client
675     :param floating_ip: the SNAPS FloatingIp object
676     :return: hopefully the same floating IP object input
677     """
678     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
679                  floating_ip.ip)
680     fips = neutron.list_floatingips(ip=floating_ip.id)
681
682     for fip in fips['floatingips']:
683         if fip['id'] == floating_ip.id:
684             return fip
685
686
687 def delete_floating_ip(neutron, floating_ip):
688     """
689     Responsible for deleting a floating IP
690     :param neutron: the Neutron client
691     :param floating_ip: the SNAPS FloatingIp object
692     :return:
693     """
694     logger.debug('Attempting to delete existing floating ip with IP - %s',
695                  floating_ip.ip)
696     return neutron.delete_floatingip(floating_ip.id)
697
698
699 def get_network_quotas(neutron, project_id):
700     """
701     Returns a list of all available keypairs
702     :param neutron: the neutron client
703     :param project_id: the project's ID of the quotas to lookup
704     :return: an object of type NetworkQuotas or None if not found
705     """
706     quota = neutron.show_quota(project_id)
707     if quota:
708         return NetworkQuotas(**quota['quota'])
709
710
711 def update_quotas(neutron, project_id, network_quotas):
712     """
713     Updates the networking quotas for a given project
714     :param neutron: the Neutron client
715     :param project_id: the project's ID that requires quota updates
716     :param network_quotas: an object of type NetworkQuotas containing the
717                            values to update
718     :return:
719     """
720     update_body = dict()
721     update_body['security_group'] = network_quotas.security_group
722     update_body['security_group_rule'] = network_quotas.security_group_rule
723     update_body['floatingip'] = network_quotas.floatingip
724     update_body['network'] = network_quotas.network
725     update_body['port'] = network_quotas.port
726     update_body['router'] = network_quotas.router
727     update_body['subnet'] = network_quotas.subnet
728
729     return neutron.update_quota(project_id, {'quota': update_body})
730
731
732 class NeutronException(Exception):
733     """
734     Exception when calls to the Keystone client cannot be served properly
735     """