1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 from neutronclient.common.exceptions import NotFound
18 from neutronclient.neutron.client import Client
20 from snaps.domain.network import (
21 Port, SecurityGroup, SecurityGroupRule, Router, InterfaceRouter, Subnet,
23 from snaps.domain.project import NetworkQuotas
24 from snaps.domain.vm_inst import FloatingIp
25 from snaps.openstack.utils import keystone_utils
27 __author__ = 'spisarski'
29 logger = logging.getLogger('neutron_utils')
32 Utilities for basic neutron API calls
36 def neutron_client(os_creds):
38 Instantiates and returns a client for communications with OpenStack's
40 :param os_creds: the credentials for connecting to the OpenStack remote API
41 :return: the client object
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)
48 def create_network(neutron, os_creds, network_settings):
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
56 :return: a SNAPS-OO Network domain object
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'])
64 raise NeutronException('Failded to create network')
67 def delete_network(neutron, network):
69 Deletes a network for OpenStack
70 :param neutron: the client
71 :param network: a SNAPS-OO Network domain object
73 if neutron and network:
74 logger.info('Deleting network with name ' + network.name)
75 neutron.delete_network(network.id)
78 def get_network(neutron, network_settings=None, network_name=None,
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
93 net_filter['name'] = network_settings.name
95 net_filter['name'] = network_name
98 net_filter['project_id'] = project_id
100 networks = neutron.list_networks(**net_filter)
101 for network, netInsts in networks.items():
102 for inst in netInsts:
103 return Network(**inst)
106 def get_network_by_id(neutron, network_id):
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
113 networks = neutron.list_networks(**{'id': network_id})
114 for network in networks['networks']:
115 if network['id'] == network_id:
116 return Network(**network)
119 def create_subnet(neutron, subnet_settings, os_creds, network=None):
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
127 :param os_creds: the OpenStack credentials
128 :return: a SNAPS-OO Subnet domain object
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])
137 raise NeutronException('Failed to create subnet')
140 def delete_subnet(neutron, subnet):
142 Deletes a network subnet for OpenStack
143 :param neutron: the client
144 :param subnet: a SNAPS-OO Subnet domain object
146 if neutron and subnet:
147 logger.info('Deleting subnet with name ' + subnet.name)
148 neutron.delete_subnet(subnet.id)
151 def get_subnet(neutron, subnet_settings=None, subnet_name=None):
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
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
167 if subnet_settings.enable_dhcp is not None:
168 sub_filter['enable_dhcp'] = subnet_settings.enable_dhcp
170 if subnet_settings.destination:
171 sub_filter['destination'] = subnet_settings.destination
173 if subnet_settings.nexthop:
174 sub_filter['nexthop'] = subnet_settings.nexthop
176 if subnet_settings.ipv6_ra_mode:
177 sub_filter['ipv6_ra_mode'] = subnet_settings.ipv6_ra_mode
179 if subnet_settings.ipv6_address_mode:
180 sub_filter['ipv6_address_mode'] = subnet_settings.ipv6_address_mode
182 sub_filter['name'] = subnet_name
186 subnets = neutron.list_subnets(**sub_filter)
187 for subnet in subnets['subnets']:
188 return Subnet(**subnet)
191 def get_subnet_by_id(neutron, subnet_id):
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
198 os_subnet = neutron.show_subnet(subnet_id)
199 if os_subnet and 'subnet' in os_subnet:
200 return Subnet(**os_subnet['subnet'])
203 def get_subnets_by_network(neutron, network):
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
212 os_subnets = neutron.list_subnets(network_id=network.id)
214 for os_subnet in os_subnets['subnets']:
215 out.append(Subnet(**os_subnet))
220 def create_router(neutron, os_creds, router_settings):
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
228 :return: a SNAPS-OO Router domain object
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'])
236 logger.error("Failed to create router.")
237 raise NeutronException('Failed to create router')
240 def delete_router(neutron, router):
242 Deletes a router for OpenStack
243 :param neutron: the client
244 :param router: a SNAPS-OO Router domain object
246 if neutron and router:
247 logger.info('Deleting router with name - ' + router.name)
248 neutron.delete_router(router=router.id)
251 def get_router(neutron, router_settings=None, router_name=None):
253 Returns the first router object (dictionary) found the given the settings
254 values if not None, else finds the first with the value of the router_name
256 :param neutron: the client
257 :param router_settings: the RouterSettings object
258 :param router_name: the name of the network to retrieve
259 :return: a SNAPS-OO Router domain object
261 router_filter = dict()
263 router_filter['name'] = router_settings.name
264 if router_settings.admin_state_up is not None:
265 router_filter['admin_state_up'] = router_settings.admin_state_up
267 router_filter['name'] = router_name
271 routers = neutron.list_routers(**router_filter)
272 for routerInst in routers['routers']:
273 return Router(**routerInst)
277 def add_interface_router(neutron, router, subnet=None, port=None):
279 Adds an interface router for OpenStack for either a subnet or port.
280 Exception will be raised if requesting for both.
281 :param neutron: the client
282 :param router: the router object
283 :param subnet: the subnet object
284 :param port: the port object
285 :return: the interface router object
288 raise NeutronException(
289 'Cannot add interface to the router. Both subnet and '
290 'port were sent in. Either or please.')
292 if neutron and router and (router or subnet):
293 logger.info('Adding interface to router with name ' + router.name)
294 os_intf_router = neutron.add_interface_router(
295 router=router.id, body=__create_port_json_body(subnet, port))
296 return InterfaceRouter(**os_intf_router)
298 raise NeutronException(
299 'Unable to create interface router as neutron client,'
300 ' router or subnet were not created')
303 def remove_interface_router(neutron, router, subnet=None, port=None):
305 Removes an interface router for OpenStack
306 :param neutron: the client
307 :param router: the SNAPS-OO Router domain object
308 :param subnet: the subnet object (either subnet or port, not both)
309 :param port: the port object
313 logger.info('Removing router interface from router named ' +
315 neutron.remove_interface_router(
317 body=__create_port_json_body(subnet, port))
318 except NotFound as e:
319 logger.warning('Could not remove router interface. NotFound - %s',
323 logger.warning('Could not remove router interface, No router object')
326 def __create_port_json_body(subnet=None, port=None):
328 Returns the dictionary required for creating and deleting router
329 interfaces. Will only work on a subnet or port object. Will throw and
330 exception if parameters contain both or neither
331 :param subnet: the subnet object
332 :param port: the port object
336 raise NeutronException(
337 'Cannot create JSON body with both subnet and port')
338 if not subnet and not port:
339 raise NeutronException(
340 'Cannot create JSON body without subnet or port')
343 return {"subnet_id": subnet.id}
345 return {"port_id": port.id}
348 def create_port(neutron, os_creds, port_settings):
350 Creates a port for OpenStack
351 :param neutron: the client
352 :param os_creds: the OpenStack credentials
353 :param port_settings: the settings object for port configuration
354 :return: the SNAPS-OO Port domain object
356 json_body = port_settings.dict_for_neutron(neutron, os_creds)
357 logger.info('Creating port for network with name - %s',
358 port_settings.network_name)
359 os_port = neutron.create_port(body=json_body)['port']
360 return Port(name=os_port['name'], id=os_port['id'],
361 ips=os_port['fixed_ips'],
362 mac_address=os_port['mac_address'],
363 allowed_address_pairs=os_port['allowed_address_pairs'])
366 def delete_port(neutron, port):
368 Removes an OpenStack port
369 :param neutron: the client
370 :param port: the SNAPS-OO Port domain object
372 logger.info('Deleting port with name ' + port.name)
373 neutron.delete_port(port.id)
376 def get_port(neutron, port_settings=None, port_name=None):
378 Returns the first port object (dictionary) found for the given query
379 :param neutron: the client
380 :param port_settings: the PortSettings object used for generating the query
381 :param port_name: if port_settings is None, this name is the value to place
383 :return: a SNAPS-OO Port domain object
388 port_filter['name'] = port_settings.name
389 if port_settings.admin_state_up:
390 port_filter['admin_state_up'] = port_settings.admin_state_up
391 if port_settings.device_id:
392 port_filter['device_id'] = port_settings.device_id
393 if port_settings.mac_address:
394 port_filter['mac_address'] = port_settings.mac_address
396 port_filter['name'] = port_name
398 ports = neutron.list_ports(**port_filter)
399 for port in ports['ports']:
400 return Port(name=port['name'], id=port['id'],
401 ips=port['fixed_ips'], mac_address=port['mac_address'])
405 def create_security_group(neutron, keystone, sec_grp_settings):
407 Creates a security group object in OpenStack
408 :param neutron: the Neutron client
409 :param keystone: the Keystone client
410 :param sec_grp_settings: the security group settings
411 :return: a SNAPS-OO SecurityGroup domain object
413 logger.info('Creating security group with name - %s',
414 sec_grp_settings.name)
415 os_group = neutron.create_security_group(
416 sec_grp_settings.dict_for_neutron(keystone))
417 return SecurityGroup(**os_group['security_group'])
420 def delete_security_group(neutron, sec_grp):
422 Deletes a security group object from OpenStack
423 :param neutron: the client
424 :param sec_grp: the SNAPS SecurityGroup object to delete
426 logger.info('Deleting security group with name - %s', sec_grp.name)
427 neutron.delete_security_group(sec_grp.id)
430 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
433 Returns the first security group for a given query. The query gets built
434 from the sec_grp_settings parameter if not None, else only the name of
435 the security group will be used, else if the query parameters are None then
436 None will be returned
437 :param neutron: the client
438 :param sec_grp_settings: an instance of SecurityGroupSettings config object
439 :param sec_grp_name: the name of security group object to retrieve
440 :param project_id: the ID of the project/tentant object that owns the
441 secuity group to retrieve
442 :return: a SNAPS-OO SecurityGroup domain object or None if not found
445 sec_grp_filter = dict()
447 sec_grp_filter['tenant_id'] = project_id
450 sec_grp_filter['name'] = sec_grp_settings.name
452 if sec_grp_settings.description:
453 sec_grp_filter['description'] = sec_grp_settings.description
455 sec_grp_filter['name'] = sec_grp_name
459 groups = neutron.list_security_groups(**sec_grp_filter)
460 for group in groups['security_groups']:
461 return SecurityGroup(**group)
464 def get_security_group_by_id(neutron, sec_grp_id):
466 Returns the first security group object of the given name else None
467 :param neutron: the client
468 :param sec_grp_id: the id of the security group to retrieve
469 :return: a SNAPS-OO SecurityGroup domain object or None if not found
471 logger.info('Retrieving security group with ID - ' + sec_grp_id)
473 groups = neutron.list_security_groups(**{'id': sec_grp_id})
474 for group in groups['security_groups']:
475 if group['id'] == sec_grp_id:
476 return SecurityGroup(**group)
480 def create_security_group_rule(neutron, sec_grp_rule_settings):
482 Creates a security group object in OpenStack
483 :param neutron: the client
484 :param sec_grp_rule_settings: the security group rule settings
485 :return: a SNAPS-OO SecurityGroupRule domain object
487 logger.info('Creating security group to security group - %s',
488 sec_grp_rule_settings.sec_grp_name)
489 os_rule = neutron.create_security_group_rule(
490 sec_grp_rule_settings.dict_for_neutron(neutron))
491 return SecurityGroupRule(**os_rule['security_group_rule'])
494 def delete_security_group_rule(neutron, sec_grp_rule):
496 Deletes a security group object from OpenStack
497 :param neutron: the client
498 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
500 logger.info('Deleting security group rule with ID - %s',
502 neutron.delete_security_group_rule(sec_grp_rule.id)
505 def get_rules_by_security_group(neutron, sec_grp):
507 Retrieves all of the rules for a given security group
508 :param neutron: the client
509 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
511 logger.info('Retrieving security group rules associate with the '
512 'security group - %s', sec_grp.name)
514 rules = neutron.list_security_group_rules(
515 **{'security_group_id': sec_grp.id})
516 for rule in rules['security_group_rules']:
517 if rule['security_group_id'] == sec_grp.id:
518 out.append(SecurityGroupRule(**rule))
522 def get_rule_by_id(neutron, sec_grp, rule_id):
524 Deletes a security group object from OpenStack
525 :param neutron: the client
526 :param sec_grp: the SNAPS SecurityGroup domain object
527 :param rule_id: the rule's ID
528 :param sec_grp: a SNAPS SecurityGroupRule domain object
530 rules = neutron.list_security_group_rules(
531 **{'security_group_id': sec_grp.id})
532 for rule in rules['security_group_rules']:
533 if rule['id'] == rule_id:
534 return SecurityGroupRule(**rule)
538 def get_external_networks(neutron):
540 Returns a list of external OpenStack network object/dict for all external
542 :param neutron: the client
543 :return: a list of external networks of Type SNAPS-OO domain class Network
546 for network in neutron.list_networks(
547 **{'router:external': True})['networks']:
548 out.append(Network(**network))
552 def get_floating_ips(neutron, ports=None):
554 Returns all of the floating IPs
555 When ports is not None, FIPs returned must be associated with one of the
556 ports in the list and a tuple 2 where the first element being the port's
557 name and the second being the FloatingIp SNAPS-OO domain object.
558 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
560 :param neutron: the Neutron client
561 :param ports: a list of SNAPS-OO Port objects to join
562 :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
563 is not None else a list of Port objects
566 fips = neutron.list_floatingips()
567 for fip in fips['floatingips']:
569 for port_name, port in ports:
570 if fip['port_id'] == port.id:
571 out.append((port.name, FloatingIp(
572 inst_id=fip['id'], ip=fip['floating_ip_address'])))
575 out.append(FloatingIp(inst_id=fip['id'],
576 ip=fip['floating_ip_address']))
581 def create_floating_ip(neutron, ext_net_name):
583 Returns the floating IP object that was created with this call
584 :param neutron: the Neutron client
585 :param ext_net_name: the name of the external network on which to apply the
587 :return: the SNAPS FloatingIp object
589 logger.info('Creating floating ip to external network - ' + ext_net_name)
590 ext_net = get_network(neutron, network_name=ext_net_name)
592 fip = neutron.create_floatingip(
594 {'floating_network_id': ext_net.id}})
596 return FloatingIp(inst_id=fip['floatingip']['id'],
597 ip=fip['floatingip']['floating_ip_address'])
599 raise NeutronException(
600 'Cannot create floating IP, external network not found')
603 def get_floating_ip(neutron, floating_ip):
605 Returns a floating IP object that should be identical to the floating_ip
607 :param neutron: the Neutron client
608 :param floating_ip: the SNAPS FloatingIp object
609 :return: hopefully the same floating IP object input
611 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
613 os_fip = __get_os_floating_ip(neutron, floating_ip)
616 inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
619 def __get_os_floating_ip(neutron, floating_ip):
621 Returns an OpenStack floating IP object
623 :param neutron: the Neutron client
624 :param floating_ip: the SNAPS FloatingIp object
625 :return: hopefully the same floating IP object input
627 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
629 fips = neutron.list_floatingips(ip=floating_ip.id)
631 for fip in fips['floatingips']:
632 if fip['id'] == floating_ip.id:
636 def delete_floating_ip(neutron, floating_ip):
638 Responsible for deleting a floating IP
639 :param neutron: the Neutron client
640 :param floating_ip: the SNAPS FloatingIp object
643 logger.debug('Attempting to delete existing floating ip with IP - %s',
645 return neutron.delete_floatingip(floating_ip.id)
648 def get_network_quotas(neutron, project_id):
650 Returns a list of all available keypairs
651 :param nova: the Nova client
652 :param project_id: the project's ID of the quotas to lookup
653 :return: an object of type NetworkQuotas or None if not found
655 quota = neutron.show_quota(project_id)
657 return NetworkQuotas(**quota['quota'])
660 def update_quotas(neutron, project_id, network_quotas):
662 Updates the networking quotas for a given project
663 :param neutron: the Neutron client
664 :param project_id: the project's ID that requires quota updates
665 :param network_quotas: an object of type NetworkQuotas containing the
670 update_body['security_group'] = network_quotas.security_group
671 update_body['security_group_rule'] = network_quotas.security_group_rule
672 update_body['floatingip'] = network_quotas.floatingip
673 update_body['network'] = network_quotas.network
674 update_body['port'] = network_quotas.port
675 update_body['router'] = network_quotas.router
676 update_body['subnet'] = network_quotas.subnet
678 return neutron.update_quota(project_id, {'quota': update_body})
681 class NeutronException(Exception):
683 Exception when calls to the Keystone client cannot be served properly