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 __map_router(neutron, 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_by_id(neutron, router_id):
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
258 router = neutron.show_router(router_id)
260 return __map_router(neutron, router['router'])
263 def get_router(neutron, router_settings=None, router_name=None):
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
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
273 router_filter = dict()
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
279 router_filter['name'] = router_name
283 routers = neutron.list_routers(**router_filter)
285 for routerInst in routers['routers']:
286 return __map_router(neutron, routerInst)
291 def __map_router(neutron, os_router):
293 Takes an OpenStack router instance and maps it to a SNAPS Router domain
295 :param neutron: the neutron client
296 :param os_router: the OpenStack Router object
299 device_ports = neutron.list_ports(
300 **{'device_id': os_router['id']})['ports']
301 port_subnets = list()
303 # Order by create date
304 sorted_ports = sorted(device_ports, key=lambda dev_port: dev_port['created_at'])
306 for port in sorted_ports:
308 for fixed_ip in port['fixed_ips']:
309 subnet = get_subnet_by_id(neutron, fixed_ip['subnet_id'])
310 if subnet and subnet.network_id == port['network_id']:
311 subnets.append(subnet)
312 port_subnets.append((Port(**port), subnets))
314 os_router['port_subnets'] = port_subnets
315 return Router(**os_router)
318 def add_interface_router(neutron, router, subnet=None, port=None):
320 Adds an interface router for OpenStack for either a subnet or port.
321 Exception will be raised if requesting for both.
322 :param neutron: the client
323 :param router: the router object
324 :param subnet: the subnet object
325 :param port: the port object
326 :return: the interface router object
329 raise NeutronException(
330 'Cannot add interface to the router. Both subnet and '
331 'port were sent in. Either or please.')
333 if neutron and router and (router or subnet):
334 logger.info('Adding interface to router with name ' + router.name)
335 os_intf_router = neutron.add_interface_router(
336 router=router.id, body=__create_port_json_body(subnet, port))
337 return InterfaceRouter(**os_intf_router)
339 raise NeutronException(
340 'Unable to create interface router as neutron client,'
341 ' router or subnet were not created')
344 def remove_interface_router(neutron, router, subnet=None, port=None):
346 Removes an interface router for OpenStack
347 :param neutron: the client
348 :param router: the SNAPS-OO Router domain object
349 :param subnet: the subnet object (either subnet or port, not both)
350 :param port: the port object
354 logger.info('Removing router interface from router named ' +
356 neutron.remove_interface_router(
358 body=__create_port_json_body(subnet, port))
359 except NotFound as e:
360 logger.warning('Could not remove router interface. NotFound - %s',
364 logger.warning('Could not remove router interface, No router object')
367 def __create_port_json_body(subnet=None, port=None):
369 Returns the dictionary required for creating and deleting router
370 interfaces. Will only work on a subnet or port object. Will throw and
371 exception if parameters contain both or neither
372 :param subnet: the subnet object
373 :param port: the port object
377 raise NeutronException(
378 'Cannot create JSON body with both subnet and port')
379 if not subnet and not port:
380 raise NeutronException(
381 'Cannot create JSON body without subnet or port')
384 return {"subnet_id": subnet.id}
386 return {"port_id": port.id}
389 def create_port(neutron, os_creds, port_settings):
391 Creates a port for OpenStack
392 :param neutron: the client
393 :param os_creds: the OpenStack credentials
394 :param port_settings: the settings object for port configuration
395 :return: the SNAPS-OO Port domain object
397 json_body = port_settings.dict_for_neutron(neutron, os_creds)
398 logger.info('Creating port for network with name - %s',
399 port_settings.network_name)
400 os_port = neutron.create_port(body=json_body)['port']
401 return Port(name=os_port['name'], id=os_port['id'],
402 ips=os_port['fixed_ips'],
403 mac_address=os_port['mac_address'],
404 allowed_address_pairs=os_port['allowed_address_pairs'])
407 def delete_port(neutron, port):
409 Removes an OpenStack port
410 :param neutron: the client
411 :param port: the SNAPS-OO Port domain object
413 logger.info('Deleting port with name ' + port.name)
414 neutron.delete_port(port.id)
417 def get_port(neutron, port_settings=None, port_name=None):
419 Returns the first port object (dictionary) found for the given query
420 :param neutron: the client
421 :param port_settings: the PortSettings object used for generating the query
422 :param port_name: if port_settings is None, this name is the value to place
424 :return: a SNAPS-OO Port domain object
429 if port_settings.name and len(port_settings.name) > 0:
430 port_filter['name'] = port_settings.name
431 if port_settings.admin_state_up:
432 port_filter['admin_state_up'] = port_settings.admin_state_up
433 if port_settings.device_id:
434 port_filter['device_id'] = port_settings.device_id
435 if port_settings.mac_address:
436 port_filter['mac_address'] = port_settings.mac_address
437 if port_settings.network_name:
438 network = get_network(neutron,
439 network_name=port_settings.network_name)
440 port_filter['network_id'] = network.id
442 port_filter['name'] = port_name
444 ports = neutron.list_ports(**port_filter)
445 for port in ports['ports']:
450 def get_port_by_id(neutron, port_id):
452 Returns a SNAPS-OO Port domain object for the given ID or none if not found
453 :param neutron: the client
454 :param port_id: the to query
455 :return: a SNAPS-OO Port domain object or None
457 port = neutron.show_port(port_id)
459 return Port(**port['port'])
463 def get_ports(neutron, network, ips=None):
465 Returns a list of SNAPS-OO Port objects for all OpenStack Port objects that
466 are associated with the 'network' parameter
467 :param neutron: the client
468 :param network: SNAPS-OO Network domain object
469 :param ips: the IPs to lookup if not None
470 :return: a SNAPS-OO Port domain object or None if not found
473 ports = neutron.list_ports(**{'network_id': network.id})
474 for port in ports['ports']:
476 for fixed_ips in port['fixed_ips']:
477 if ('ip_address' in fixed_ips and
478 fixed_ips['ip_address'] in ips) or ips is None:
479 out.append(Port(**port))
482 out.append(Port(**port))
487 def create_security_group(neutron, keystone, sec_grp_settings):
489 Creates a security group object in OpenStack
490 :param neutron: the Neutron client
491 :param keystone: the Keystone client
492 :param sec_grp_settings: the security group settings
493 :return: a SNAPS-OO SecurityGroup domain object
495 logger.info('Creating security group with name - %s',
496 sec_grp_settings.name)
497 os_group = neutron.create_security_group(
498 sec_grp_settings.dict_for_neutron(keystone))
499 return __map_os_security_group(neutron, os_group['security_group'])
502 def delete_security_group(neutron, sec_grp):
504 Deletes a security group object from OpenStack
505 :param neutron: the client
506 :param sec_grp: the SNAPS SecurityGroup object to delete
508 logger.info('Deleting security group with name - %s', sec_grp.name)
509 neutron.delete_security_group(sec_grp.id)
512 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
515 Returns the first security group for a given query. The query gets built
516 from the sec_grp_settings parameter if not None, else only the name of
517 the security group will be used, else if the query parameters are None then
518 None will be returned
519 :param neutron: the client
520 :param sec_grp_settings: an instance of SecurityGroupSettings config object
521 :param sec_grp_name: the name of security group object to retrieve
522 :param project_id: the ID of the project/tentant object that owns the
523 secuity group to retrieve
524 :return: a SNAPS-OO SecurityGroup domain object or None if not found
527 sec_grp_filter = dict()
529 sec_grp_filter['tenant_id'] = project_id
532 sec_grp_filter['name'] = sec_grp_settings.name
534 if sec_grp_settings.description:
535 sec_grp_filter['description'] = sec_grp_settings.description
537 sec_grp_filter['name'] = sec_grp_name
541 groups = neutron.list_security_groups(**sec_grp_filter)
542 for group in groups['security_groups']:
543 return __map_os_security_group(neutron, group)
546 def __map_os_security_group(neutron, os_sec_grp):
548 Creates a SecurityGroup SNAPS domain object from an OpenStack Security
550 :param neutron: the neutron client for performing rule lookups
551 :param os_sec_grp: the OpenStack Security Group dict object
552 :return: a SecurityGroup object
554 os_sec_grp['rules'] = get_rules_by_security_group_id(
555 neutron, os_sec_grp['id'])
556 return SecurityGroup(**os_sec_grp)
559 def get_security_group_by_id(neutron, sec_grp_id):
561 Returns the first security group object of the given name else None
562 :param neutron: the client
563 :param sec_grp_id: the id of the security group to retrieve
564 :return: a SNAPS-OO SecurityGroup domain object or None if not found
566 logger.info('Retrieving security group with ID - ' + sec_grp_id)
568 groups = neutron.list_security_groups(**{'id': sec_grp_id})
569 for group in groups['security_groups']:
570 if group['id'] == sec_grp_id:
571 return __map_os_security_group(neutron, group)
575 def create_security_group_rule(neutron, sec_grp_rule_settings):
577 Creates a security group object in OpenStack
578 :param neutron: the client
579 :param sec_grp_rule_settings: the security group rule settings
580 :return: a SNAPS-OO SecurityGroupRule domain object
582 logger.info('Creating security group to security group - %s',
583 sec_grp_rule_settings.sec_grp_name)
584 os_rule = neutron.create_security_group_rule(
585 sec_grp_rule_settings.dict_for_neutron(neutron))
586 return SecurityGroupRule(**os_rule['security_group_rule'])
589 def delete_security_group_rule(neutron, sec_grp_rule):
591 Deletes a security group object from OpenStack
592 :param neutron: the client
593 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
595 logger.info('Deleting security group rule with ID - %s',
597 neutron.delete_security_group_rule(sec_grp_rule.id)
600 def get_rules_by_security_group(neutron, sec_grp):
602 Retrieves all of the rules for a given security group
603 :param neutron: the client
604 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
606 return get_rules_by_security_group_id(neutron, sec_grp.id)
609 def get_rules_by_security_group_id(neutron, sec_grp_id):
611 Retrieves all of the rules for a given security group
612 :param neutron: the client
613 :param sec_grp_id: the ID of the associated security group
615 logger.info('Retrieving security group rules associate with the '
616 'security group with ID - %s', sec_grp_id)
618 rules = neutron.list_security_group_rules(
619 **{'security_group_id': sec_grp_id})
620 for rule in rules['security_group_rules']:
621 if rule['security_group_id'] == sec_grp_id:
622 out.append(SecurityGroupRule(**rule))
626 def get_rule_by_id(neutron, sec_grp, rule_id):
628 Deletes a security group object from OpenStack
629 :param neutron: the client
630 :param sec_grp: the SNAPS SecurityGroup domain object
631 :param rule_id: the rule's ID
632 :param sec_grp: a SNAPS SecurityGroupRule domain object
634 rules = neutron.list_security_group_rules(
635 **{'security_group_id': sec_grp.id})
636 for rule in rules['security_group_rules']:
637 if rule['id'] == rule_id:
638 return SecurityGroupRule(**rule)
642 def get_external_networks(neutron):
644 Returns a list of external OpenStack network object/dict for all external
646 :param neutron: the client
647 :return: a list of external networks of Type SNAPS-OO domain class Network
650 for network in neutron.list_networks(
651 **{'router:external': True})['networks']:
652 out.append(Network(**network))
656 def get_floating_ips(neutron, ports=None):
658 Returns all of the floating IPs
659 When ports is not None, FIPs returned must be associated with one of the
660 ports in the list and a tuple 2 where the first element being the port's
661 ID and the second being the FloatingIp SNAPS-OO domain object.
662 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
664 :param neutron: the Neutron client
665 :param ports: a list of tuple 2 where index 0 is the port name and index 1
666 is the SNAPS-OO Port object
667 :return: a list of tuple 2 (port_id, SNAPS FloatingIp) objects when ports
668 is not None else a list of Port objects
671 fips = neutron.list_floatingips()
672 for fip in fips['floatingips']:
674 for port_name, port in ports:
675 if port and port.id == fip['port_id']:
676 out.append((port.id, FloatingIp(**fip)))
679 out.append(FloatingIp(**fip))
684 def create_floating_ip(neutron, ext_net_name):
686 Returns the floating IP object that was created with this call
687 :param neutron: the Neutron client
688 :param ext_net_name: the name of the external network on which to apply the
690 :return: the SNAPS FloatingIp object
692 logger.info('Creating floating ip to external network - ' + ext_net_name)
693 ext_net = get_network(neutron, network_name=ext_net_name)
695 fip = neutron.create_floatingip(
697 {'floating_network_id': ext_net.id}})
699 return FloatingIp(id=fip['floatingip']['id'],
700 ip=fip['floatingip']['floating_ip_address'])
702 raise NeutronException(
703 'Cannot create floating IP, external network not found')
706 def get_floating_ip(neutron, floating_ip):
708 Returns a floating IP object that should be identical to the floating_ip
710 :param neutron: the Neutron client
711 :param floating_ip: the SNAPS FloatingIp object
712 :return: hopefully the same floating IP object input
714 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
716 os_fip = __get_os_floating_ip(neutron, floating_ip)
718 return FloatingIp(id=os_fip['id'], ip=os_fip['floating_ip_address'])
721 def __get_os_floating_ip(neutron, floating_ip):
723 Returns an OpenStack floating IP object
725 :param neutron: the Neutron client
726 :param floating_ip: the SNAPS FloatingIp object
727 :return: hopefully the same floating IP object input
729 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
731 fips = neutron.list_floatingips(ip=floating_ip.id)
733 for fip in fips['floatingips']:
734 if fip['id'] == floating_ip.id:
738 def delete_floating_ip(neutron, floating_ip):
740 Responsible for deleting a floating IP
741 :param neutron: the Neutron client
742 :param floating_ip: the SNAPS FloatingIp object
745 logger.debug('Attempting to delete existing floating ip with IP - %s',
747 return neutron.delete_floatingip(floating_ip.id)
750 def get_network_quotas(neutron, project_id):
752 Returns a list of all available keypairs
753 :param neutron: the neutron client
754 :param project_id: the project's ID of the quotas to lookup
755 :return: an object of type NetworkQuotas or None if not found
757 quota = neutron.show_quota(project_id)
759 return NetworkQuotas(**quota['quota'])
762 def update_quotas(neutron, project_id, network_quotas):
764 Updates the networking quotas for a given project
765 :param neutron: the Neutron client
766 :param project_id: the project's ID that requires quota updates
767 :param network_quotas: an object of type NetworkQuotas containing the
772 update_body['security_group'] = network_quotas.security_group
773 update_body['security_group_rule'] = network_quotas.security_group_rule
774 update_body['floatingip'] = network_quotas.floatingip
775 update_body['network'] = network_quotas.network
776 update_body['port'] = network_quotas.port
777 update_body['router'] = network_quotas.router
778 update_body['subnet'] = network_quotas.subnet
780 return neutron.update_quota(project_id, {'quota': update_body})
783 class NeutronException(Exception):
785 Exception when calls to the Keystone client cannot be served properly