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 create_router(neutron, os_creds, router_settings):
193 Creates a router for OpenStack
194 :param neutron: the client
195 :param os_creds: the OpenStack credentials
196 :param router_settings: A dictionary containing the router configuration
197 and is responsible for creating the subnet request
199 :return: a SNAPS-OO Router domain object
202 json_body = router_settings.dict_for_neutron(neutron, os_creds)
203 logger.info('Creating router with name - ' + router_settings.name)
204 os_router = neutron.create_router(json_body)
205 return Router(**os_router['router'])
207 logger.error("Failed to create router.")
208 raise NeutronException('Failed to create router')
211 def delete_router(neutron, router):
213 Deletes a router for OpenStack
214 :param neutron: the client
215 :param router: a SNAPS-OO Router domain object
217 if neutron and router:
218 logger.info('Deleting router with name - ' + router.name)
219 neutron.delete_router(router=router.id)
222 def get_router(neutron, router_settings=None, router_name=None):
224 Returns the first router object (dictionary) found the given the settings
225 values if not None, else finds the first with the value of the router_name
227 :param neutron: the client
228 :param router_settings: the RouterSettings object
229 :param router_name: the name of the network to retrieve
230 :return: a SNAPS-OO Router domain object
232 router_filter = dict()
234 router_filter['name'] = router_settings.name
235 if router_settings.admin_state_up is not None:
236 router_filter['admin_state_up'] = router_settings.admin_state_up
238 router_filter['name'] = router_name
242 routers = neutron.list_routers(**router_filter)
243 for routerInst in routers['routers']:
244 return Router(**routerInst)
248 def add_interface_router(neutron, router, subnet=None, port=None):
250 Adds an interface router for OpenStack for either a subnet or port.
251 Exception will be raised if requesting for both.
252 :param neutron: the client
253 :param router: the router object
254 :param subnet: the subnet object
255 :param port: the port object
256 :return: the interface router object
259 raise NeutronException(
260 'Cannot add interface to the router. Both subnet and '
261 'port were sent in. Either or please.')
263 if neutron and router and (router or subnet):
264 logger.info('Adding interface to router with name ' + router.name)
265 os_intf_router = neutron.add_interface_router(
266 router=router.id, body=__create_port_json_body(subnet, port))
267 return InterfaceRouter(**os_intf_router)
269 raise NeutronException(
270 'Unable to create interface router as neutron client,'
271 ' router or subnet were not created')
274 def remove_interface_router(neutron, router, subnet=None, port=None):
276 Removes an interface router for OpenStack
277 :param neutron: the client
278 :param router: the SNAPS-OO Router domain object
279 :param subnet: the subnet object (either subnet or port, not both)
280 :param port: the port object
284 logger.info('Removing router interface from router named ' +
286 neutron.remove_interface_router(
288 body=__create_port_json_body(subnet, port))
289 except NotFound as e:
290 logger.warning('Could not remove router interface. NotFound - %s',
294 logger.warning('Could not remove router interface, No router object')
297 def __create_port_json_body(subnet=None, port=None):
299 Returns the dictionary required for creating and deleting router
300 interfaces. Will only work on a subnet or port object. Will throw and
301 exception if parameters contain both or neither
302 :param subnet: the subnet object
303 :param port: the port object
307 raise NeutronException(
308 'Cannot create JSON body with both subnet and port')
309 if not subnet and not port:
310 raise NeutronException(
311 'Cannot create JSON body without subnet or port')
314 return {"subnet_id": subnet.id}
316 return {"port_id": port.id}
319 def create_port(neutron, os_creds, port_settings):
321 Creates a port for OpenStack
322 :param neutron: the client
323 :param os_creds: the OpenStack credentials
324 :param port_settings: the settings object for port configuration
325 :return: the SNAPS-OO Port domain object
327 json_body = port_settings.dict_for_neutron(neutron, os_creds)
328 logger.info('Creating port for network with name - %s',
329 port_settings.network_name)
330 os_port = neutron.create_port(body=json_body)['port']
331 return Port(name=os_port['name'], id=os_port['id'],
332 ips=os_port['fixed_ips'],
333 mac_address=os_port['mac_address'],
334 allowed_address_pairs=os_port['allowed_address_pairs'])
337 def delete_port(neutron, port):
339 Removes an OpenStack port
340 :param neutron: the client
341 :param port: the SNAPS-OO Port domain object
343 logger.info('Deleting port with name ' + port.name)
344 neutron.delete_port(port.id)
347 def get_port(neutron, port_settings=None, port_name=None):
349 Returns the first port object (dictionary) found for the given query
350 :param neutron: the client
351 :param port_settings: the PortSettings object used for generating the query
352 :param port_name: if port_settings is None, this name is the value to place
354 :return: a SNAPS-OO Port domain object
359 port_filter['name'] = port_settings.name
360 if port_settings.admin_state_up:
361 port_filter['admin_state_up'] = port_settings.admin_state_up
362 if port_settings.device_id:
363 port_filter['device_id'] = port_settings.device_id
364 if port_settings.mac_address:
365 port_filter['mac_address'] = port_settings.mac_address
367 port_filter['name'] = port_name
369 ports = neutron.list_ports(**port_filter)
370 for port in ports['ports']:
371 return Port(name=port['name'], id=port['id'],
372 ips=port['fixed_ips'], mac_address=port['mac_address'])
376 def create_security_group(neutron, keystone, sec_grp_settings):
378 Creates a security group object in OpenStack
379 :param neutron: the Neutron client
380 :param keystone: the Keystone client
381 :param sec_grp_settings: the security group settings
382 :return: a SNAPS-OO SecurityGroup domain object
384 logger.info('Creating security group with name - %s',
385 sec_grp_settings.name)
386 os_group = neutron.create_security_group(
387 sec_grp_settings.dict_for_neutron(keystone))
388 return SecurityGroup(**os_group['security_group'])
391 def delete_security_group(neutron, sec_grp):
393 Deletes a security group object from OpenStack
394 :param neutron: the client
395 :param sec_grp: the SNAPS SecurityGroup object to delete
397 logger.info('Deleting security group with name - %s', sec_grp.name)
398 neutron.delete_security_group(sec_grp.id)
401 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
404 Returns the first security group for a given query. The query gets built
405 from the sec_grp_settings parameter if not None, else only the name of
406 the security group will be used, else if the query parameters are None then
407 None will be returned
408 :param neutron: the client
409 :param sec_grp_settings: an instance of SecurityGroupSettings config object
410 :param sec_grp_name: the name of security group object to retrieve
411 :param project_id: the ID of the project/tentant object that owns the
412 secuity group to retrieve
413 :return: a SNAPS-OO SecurityGroup domain object or None if not found
416 sec_grp_filter = dict()
418 sec_grp_filter['tenant_id'] = project_id
421 sec_grp_filter['name'] = sec_grp_settings.name
423 if sec_grp_settings.description:
424 sec_grp_filter['description'] = sec_grp_settings.description
426 sec_grp_filter['name'] = sec_grp_name
430 groups = neutron.list_security_groups(**sec_grp_filter)
431 for group in groups['security_groups']:
432 return SecurityGroup(**group)
435 def get_security_group_by_id(neutron, sec_grp_id):
437 Returns the first security group object of the given name else None
438 :param neutron: the client
439 :param sec_grp_id: the id of the security group to retrieve
440 :return: a SNAPS-OO SecurityGroup domain object or None if not found
442 logger.info('Retrieving security group with ID - ' + sec_grp_id)
444 groups = neutron.list_security_groups(**{'id': sec_grp_id})
445 for group in groups['security_groups']:
446 if group['id'] == sec_grp_id:
447 return SecurityGroup(**group)
451 def create_security_group_rule(neutron, sec_grp_rule_settings):
453 Creates a security group object in OpenStack
454 :param neutron: the client
455 :param sec_grp_rule_settings: the security group rule settings
456 :return: a SNAPS-OO SecurityGroupRule domain object
458 logger.info('Creating security group to security group - %s',
459 sec_grp_rule_settings.sec_grp_name)
460 os_rule = neutron.create_security_group_rule(
461 sec_grp_rule_settings.dict_for_neutron(neutron))
462 return SecurityGroupRule(**os_rule['security_group_rule'])
465 def delete_security_group_rule(neutron, sec_grp_rule):
467 Deletes a security group object from OpenStack
468 :param neutron: the client
469 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
471 logger.info('Deleting security group rule with ID - %s',
473 neutron.delete_security_group_rule(sec_grp_rule.id)
476 def get_rules_by_security_group(neutron, sec_grp):
478 Retrieves all of the rules for a given security group
479 :param neutron: the client
480 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
482 logger.info('Retrieving security group rules associate with the '
483 'security group - %s', sec_grp.name)
485 rules = neutron.list_security_group_rules(
486 **{'security_group_id': sec_grp.id})
487 for rule in rules['security_group_rules']:
488 if rule['security_group_id'] == sec_grp.id:
489 out.append(SecurityGroupRule(**rule))
493 def get_rule_by_id(neutron, sec_grp, rule_id):
495 Deletes a security group object from OpenStack
496 :param neutron: the client
497 :param sec_grp: the SNAPS SecurityGroup domain object
498 :param rule_id: the rule's ID
499 :param sec_grp: a SNAPS SecurityGroupRule domain object
501 rules = neutron.list_security_group_rules(
502 **{'security_group_id': sec_grp.id})
503 for rule in rules['security_group_rules']:
504 if rule['id'] == rule_id:
505 return SecurityGroupRule(**rule)
509 def get_external_networks(neutron):
511 Returns a list of external OpenStack network object/dict for all external
513 :param neutron: the client
514 :return: a list of external networks of Type SNAPS-OO domain class Network
517 for network in neutron.list_networks(
518 **{'router:external': True})['networks']:
519 out.append(Network(**network))
523 def get_floating_ips(neutron, ports=None):
525 Returns all of the floating IPs
526 When ports is not None, FIPs returned must be associated with one of the
527 ports in the list and a tuple 2 where the first element being the port's
528 name and the second being the FloatingIp SNAPS-OO domain object.
529 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
531 :param neutron: the Neutron client
532 :param ports: a list of SNAPS-OO Port objects to join
533 :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
534 is not None else a list of Port objects
537 fips = neutron.list_floatingips()
538 for fip in fips['floatingips']:
540 for port_name, port in ports:
541 if fip['port_id'] == port.id:
542 out.append((port.name, FloatingIp(
543 inst_id=fip['id'], ip=fip['floating_ip_address'])))
546 out.append(FloatingIp(inst_id=fip['id'],
547 ip=fip['floating_ip_address']))
552 def create_floating_ip(neutron, ext_net_name):
554 Returns the floating IP object that was created with this call
555 :param neutron: the Neutron client
556 :param ext_net_name: the name of the external network on which to apply the
558 :return: the SNAPS FloatingIp object
560 logger.info('Creating floating ip to external network - ' + ext_net_name)
561 ext_net = get_network(neutron, network_name=ext_net_name)
563 fip = neutron.create_floatingip(
565 {'floating_network_id': ext_net.id}})
567 return FloatingIp(inst_id=fip['floatingip']['id'],
568 ip=fip['floatingip']['floating_ip_address'])
570 raise NeutronException(
571 'Cannot create floating IP, external network not found')
574 def get_floating_ip(neutron, floating_ip):
576 Returns a floating IP object that should be identical to the floating_ip
578 :param neutron: the Neutron client
579 :param floating_ip: the SNAPS FloatingIp object
580 :return: hopefully the same floating IP object input
582 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
584 os_fip = __get_os_floating_ip(neutron, floating_ip)
587 inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
590 def __get_os_floating_ip(neutron, floating_ip):
592 Returns an OpenStack floating IP object
594 :param neutron: the Neutron client
595 :param floating_ip: the SNAPS FloatingIp object
596 :return: hopefully the same floating IP object input
598 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
600 fips = neutron.list_floatingips(ip=floating_ip.id)
602 for fip in fips['floatingips']:
603 if fip['id'] == floating_ip.id:
607 def delete_floating_ip(neutron, floating_ip):
609 Responsible for deleting a floating IP
610 :param neutron: the Neutron client
611 :param floating_ip: the SNAPS FloatingIp object
614 logger.debug('Attempting to delete existing floating ip with IP - %s',
616 return neutron.delete_floatingip(floating_ip.id)
619 def get_network_quotas(neutron, project_id):
621 Returns a list of all available keypairs
622 :param nova: the Nova client
623 :param project_id: the project's ID of the quotas to lookup
624 :return: an object of type NetworkQuotas or None if not found
626 quota = neutron.show_quota(project_id)
628 return NetworkQuotas(**quota['quota'])
631 def update_quotas(neutron, project_id, network_quotas):
633 Updates the networking quotas for a given project
634 :param neutron: the Neutron client
635 :param project_id: the project's ID that requires quota updates
636 :param network_quotas: an object of type NetworkQuotas containing the
641 update_body['security_group'] = network_quotas.security_group
642 update_body['security_group_rule'] = network_quotas.security_group_rule
643 update_body['floatingip'] = network_quotas.floatingip
644 update_body['network'] = network_quotas.network
645 update_body['port'] = network_quotas.port
646 update_body['router'] = network_quotas.router
647 update_body['subnet'] = network_quotas.subnet
649 return neutron.update_quota(project_id, {'quota': update_body})
652 class NeutronException(Exception):
654 Exception when calls to the Keystone client cannot be served properly