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.vm_inst import FloatingIp
24 from snaps.openstack.utils import keystone_utils
26 __author__ = 'spisarski'
28 logger = logging.getLogger('neutron_utils')
31 Utilities for basic neutron API calls
35 def neutron_client(os_creds):
37 Instantiates and returns a client for communications with OpenStack's
39 :param os_creds: the credentials for connecting to the OpenStack remote API
40 :return: the client object
42 return Client(api_version=os_creds.network_api_version,
43 session=keystone_utils.keystone_session(os_creds),
44 region_name=os_creds.region_name)
47 def create_network(neutron, os_creds, network_settings):
49 Creates a network for OpenStack
50 :param neutron: the client
51 :param os_creds: the OpenStack credentials
52 :param network_settings: A dictionary containing the network configuration
53 and is responsible for creating the network
55 :return: a SNAPS-OO Network domain object
57 if neutron and network_settings:
58 logger.info('Creating network with name ' + network_settings.name)
59 json_body = network_settings.dict_for_neutron(os_creds)
60 os_network = neutron.create_network(body=json_body)
61 return Network(**os_network['network'])
63 raise NeutronException('Failded to create network')
66 def delete_network(neutron, network):
68 Deletes a network for OpenStack
69 :param neutron: the client
70 :param network: a SNAPS-OO Network domain object
72 if neutron and network:
73 logger.info('Deleting network with name ' + network.name)
74 neutron.delete_network(network.id)
77 def get_network(neutron, network_settings=None, network_name=None,
80 Returns Network SNAPS-OO domain object the first network found with
81 either the given attributes from the network_settings object if not None,
82 else the query will use just the name from the network_name parameter.
83 When the project_id is included, that will be added to the query filter.
84 :param neutron: the client
85 :param network_settings: the NetworkSettings object used to create filter
86 :param network_name: the name of the network to retrieve
87 :param project_id: the id of the network's project
88 :return: a SNAPS-OO Network domain object
92 net_filter['name'] = network_settings.name
94 net_filter['name'] = network_name
97 net_filter['project_id'] = project_id
99 networks = neutron.list_networks(**net_filter)
100 for network, netInsts in networks.items():
101 for inst in netInsts:
102 return Network(**inst)
105 def get_network_by_id(neutron, network_id):
107 Returns the network object (dictionary) with the given ID else None
108 :param neutron: the client
109 :param network_id: the id of the network to retrieve
110 :return: a SNAPS-OO Network domain object
112 networks = neutron.list_networks(**{'id': network_id})
113 for network in networks['networks']:
114 if network['id'] == network_id:
115 return Network(**network)
118 def create_subnet(neutron, subnet_settings, os_creds, network=None):
120 Creates a network subnet for OpenStack
121 :param neutron: the client
122 :param network: the network object
123 :param subnet_settings: A dictionary containing the subnet configuration
124 and is responsible for creating the subnet request
126 :param os_creds: the OpenStack credentials
127 :return: a SNAPS-OO Subnet domain object
129 if neutron and network and subnet_settings:
130 json_body = {'subnets': [subnet_settings.dict_for_neutron(
131 os_creds, network=network)]}
132 logger.info('Creating subnet with name ' + subnet_settings.name)
133 subnets = neutron.create_subnet(body=json_body)
134 return Subnet(**subnets['subnets'][0])
136 raise NeutronException('Failed to create subnet')
139 def delete_subnet(neutron, subnet):
141 Deletes a network subnet for OpenStack
142 :param neutron: the client
143 :param subnet: a SNAPS-OO Subnet domain object
145 if neutron and subnet:
146 logger.info('Deleting subnet with name ' + subnet.name)
147 neutron.delete_subnet(subnet.id)
150 def get_subnet(neutron, subnet_settings=None, subnet_name=None):
152 Returns the first subnet object that fits the query else None including
153 if subnet_settings or subnet_name parameters are None.
154 :param neutron: the client
155 :param subnet_settings: the subnet settings of the object to retrieve
156 :param subnet_name: the name of the subnet to retrieve
157 :return: a SNAPS-OO Subnet domain object or None
161 sub_filter['name'] = subnet_settings.name
162 sub_filter['cidr'] = subnet_settings.cidr
163 if subnet_settings.gateway_ip:
164 sub_filter['gateway_ip'] = subnet_settings.gateway_ip
166 if subnet_settings.enable_dhcp is not None:
167 sub_filter['enable_dhcp'] = subnet_settings.enable_dhcp
169 if subnet_settings.destination:
170 sub_filter['destination'] = subnet_settings.destination
172 if subnet_settings.nexthop:
173 sub_filter['nexthop'] = subnet_settings.nexthop
175 if subnet_settings.ipv6_ra_mode:
176 sub_filter['ipv6_ra_mode'] = subnet_settings.ipv6_ra_mode
178 if subnet_settings.ipv6_address_mode:
179 sub_filter['ipv6_address_mode'] = subnet_settings.ipv6_address_mode
181 sub_filter['name'] = subnet_name
185 subnets = neutron.list_subnets(**sub_filter)
186 for subnet in subnets['subnets']:
187 return Subnet(**subnet)
190 def get_subnet_by_id(neutron, subnet_id):
192 Returns a SNAPS-OO Subnet domain object for a given ID
193 :param neutron: the OpenStack neutron client
194 :param subnet_id: the subnet ID
195 :return: a Subnet object
197 os_subnet = neutron.show_subnet(subnet_id)
198 if os_subnet and 'subnet' in os_subnet:
199 return Subnet(**os_subnet['subnet'])
202 def get_subnets_by_network(neutron, network):
204 Returns a list of SNAPS-OO Subnet domain objects
205 :param neutron: the OpenStack neutron client
206 :param network: the SNAPS-OO Network domain object
207 :return: a list of Subnet objects
211 os_subnets = neutron.list_subnets(network_id=network.id)
213 for os_subnet in os_subnets['subnets']:
214 out.append(Subnet(**os_subnet))
219 def create_router(neutron, os_creds, router_settings):
221 Creates a router for OpenStack
222 :param neutron: the client
223 :param os_creds: the OpenStack credentials
224 :param router_settings: A dictionary containing the router configuration
225 and is responsible for creating the subnet request
227 :return: a SNAPS-OO Router domain object
230 json_body = router_settings.dict_for_neutron(neutron, os_creds)
231 logger.info('Creating router with name - ' + router_settings.name)
232 os_router = neutron.create_router(json_body)
233 return Router(**os_router['router'])
235 logger.error("Failed to create router.")
236 raise NeutronException('Failed to create router')
239 def delete_router(neutron, router):
241 Deletes a router for OpenStack
242 :param neutron: the client
243 :param router: a SNAPS-OO Router domain object
245 if neutron and router:
246 logger.info('Deleting router with name - ' + router.name)
247 neutron.delete_router(router=router.id)
250 def get_router(neutron, router_settings=None, router_name=None):
252 Returns the first router object (dictionary) found the given the settings
253 values if not None, else finds the first with the value of the router_name
255 :param neutron: the client
256 :param router_settings: the RouterSettings object
257 :param router_name: the name of the network to retrieve
258 :return: a SNAPS-OO Router domain object
260 router_filter = dict()
262 router_filter['name'] = router_settings.name
263 if router_settings.admin_state_up is not None:
264 router_filter['admin_state_up'] = router_settings.admin_state_up
266 router_filter['name'] = router_name
270 routers = neutron.list_routers(**router_filter)
271 for routerInst in routers['routers']:
272 return Router(**routerInst)
276 def add_interface_router(neutron, router, subnet=None, port=None):
278 Adds an interface router for OpenStack for either a subnet or port.
279 Exception will be raised if requesting for both.
280 :param neutron: the client
281 :param router: the router object
282 :param subnet: the subnet object
283 :param port: the port object
284 :return: the interface router object
287 raise NeutronException(
288 'Cannot add interface to the router. Both subnet and '
289 'port were sent in. Either or please.')
291 if neutron and router and (router or subnet):
292 logger.info('Adding interface to router with name ' + router.name)
293 os_intf_router = neutron.add_interface_router(
294 router=router.id, body=__create_port_json_body(subnet, port))
295 return InterfaceRouter(**os_intf_router)
297 raise NeutronException(
298 'Unable to create interface router as neutron client,'
299 ' router or subnet were not created')
302 def remove_interface_router(neutron, router, subnet=None, port=None):
304 Removes an interface router for OpenStack
305 :param neutron: the client
306 :param router: the SNAPS-OO Router domain object
307 :param subnet: the subnet object (either subnet or port, not both)
308 :param port: the port object
312 logger.info('Removing router interface from router named ' +
314 neutron.remove_interface_router(
316 body=__create_port_json_body(subnet, port))
317 except NotFound as e:
318 logger.warning('Could not remove router interface. NotFound - %s',
322 logger.warning('Could not remove router interface, No router object')
325 def __create_port_json_body(subnet=None, port=None):
327 Returns the dictionary required for creating and deleting router
328 interfaces. Will only work on a subnet or port object. Will throw and
329 exception if parameters contain both or neither
330 :param subnet: the subnet object
331 :param port: the port object
335 raise NeutronException(
336 'Cannot create JSON body with both subnet and port')
337 if not subnet and not port:
338 raise NeutronException(
339 'Cannot create JSON body without subnet or port')
342 return {"subnet_id": subnet.id}
344 return {"port_id": port.id}
347 def create_port(neutron, os_creds, port_settings):
349 Creates a port for OpenStack
350 :param neutron: the client
351 :param os_creds: the OpenStack credentials
352 :param port_settings: the settings object for port configuration
353 :return: the SNAPS-OO Port domain object
355 json_body = port_settings.dict_for_neutron(neutron, os_creds)
356 logger.info('Creating port for network with name - %s',
357 port_settings.network_name)
358 os_port = neutron.create_port(body=json_body)['port']
359 return Port(name=os_port['name'], id=os_port['id'],
360 ips=os_port['fixed_ips'],
361 mac_address=os_port['mac_address'],
362 allowed_address_pairs=os_port['allowed_address_pairs'])
365 def delete_port(neutron, port):
367 Removes an OpenStack port
368 :param neutron: the client
369 :param port: the SNAPS-OO Port domain object
371 logger.info('Deleting port with name ' + port.name)
372 neutron.delete_port(port.id)
375 def get_port(neutron, port_settings=None, port_name=None):
377 Returns the first port object (dictionary) found for the given query
378 :param neutron: the client
379 :param port_settings: the PortSettings object used for generating the query
380 :param port_name: if port_settings is None, this name is the value to place
382 :return: a SNAPS-OO Port domain object
387 port_filter['name'] = port_settings.name
388 if port_settings.admin_state_up:
389 port_filter['admin_state_up'] = port_settings.admin_state_up
390 if port_settings.device_id:
391 port_filter['device_id'] = port_settings.device_id
392 if port_settings.mac_address:
393 port_filter['mac_address'] = port_settings.mac_address
395 port_filter['name'] = port_name
397 ports = neutron.list_ports(**port_filter)
398 for port in ports['ports']:
399 return Port(name=port['name'], id=port['id'],
400 ips=port['fixed_ips'], mac_address=port['mac_address'])
404 def create_security_group(neutron, keystone, sec_grp_settings):
406 Creates a security group object in OpenStack
407 :param neutron: the Neutron client
408 :param keystone: the Keystone client
409 :param sec_grp_settings: the security group settings
410 :return: a SNAPS-OO SecurityGroup domain object
412 logger.info('Creating security group with name - %s',
413 sec_grp_settings.name)
414 os_group = neutron.create_security_group(
415 sec_grp_settings.dict_for_neutron(keystone))
416 return SecurityGroup(**os_group['security_group'])
419 def delete_security_group(neutron, sec_grp):
421 Deletes a security group object from OpenStack
422 :param neutron: the client
423 :param sec_grp: the SNAPS SecurityGroup object to delete
425 logger.info('Deleting security group with name - %s', sec_grp.name)
426 neutron.delete_security_group(sec_grp.id)
429 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
432 Returns the first security group for a given query. The query gets built
433 from the sec_grp_settings parameter if not None, else only the name of
434 the security group will be used, else if the query parameters are None then
435 None will be returned
436 :param neutron: the client
437 :param sec_grp_settings: an instance of SecurityGroupSettings config object
438 :param sec_grp_name: the name of security group object to retrieve
439 :param project_id: the ID of the project/tentant object that owns the
440 secuity group to retrieve
441 :return: a SNAPS-OO SecurityGroup domain object or None if not found
444 sec_grp_filter = dict()
446 sec_grp_filter['tenant_id'] = project_id
449 sec_grp_filter['name'] = sec_grp_settings.name
451 if sec_grp_settings.description:
452 sec_grp_filter['description'] = sec_grp_settings.description
454 sec_grp_filter['name'] = sec_grp_name
458 groups = neutron.list_security_groups(**sec_grp_filter)
459 for group in groups['security_groups']:
460 return SecurityGroup(**group)
463 def get_security_group_by_id(neutron, sec_grp_id):
465 Returns the first security group object of the given name else None
466 :param neutron: the client
467 :param sec_grp_id: the id of the security group to retrieve
468 :return: a SNAPS-OO SecurityGroup domain object or None if not found
470 logger.info('Retrieving security group with ID - ' + sec_grp_id)
472 groups = neutron.list_security_groups(**{'id': sec_grp_id})
473 for group in groups['security_groups']:
474 if group['id'] == sec_grp_id:
475 return SecurityGroup(**group)
479 def create_security_group_rule(neutron, sec_grp_rule_settings):
481 Creates a security group object in OpenStack
482 :param neutron: the client
483 :param sec_grp_rule_settings: the security group rule settings
484 :return: a SNAPS-OO SecurityGroupRule domain object
486 logger.info('Creating security group to security group - %s',
487 sec_grp_rule_settings.sec_grp_name)
488 os_rule = neutron.create_security_group_rule(
489 sec_grp_rule_settings.dict_for_neutron(neutron))
490 return SecurityGroupRule(**os_rule['security_group_rule'])
493 def delete_security_group_rule(neutron, sec_grp_rule):
495 Deletes a security group object from OpenStack
496 :param neutron: the client
497 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
499 logger.info('Deleting security group rule with ID - %s',
501 neutron.delete_security_group_rule(sec_grp_rule.id)
504 def get_rules_by_security_group(neutron, sec_grp):
506 Retrieves all of the rules for a given security group
507 :param neutron: the client
508 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
510 logger.info('Retrieving security group rules associate with the '
511 'security group - %s', sec_grp.name)
513 rules = neutron.list_security_group_rules(
514 **{'security_group_id': sec_grp.id})
515 for rule in rules['security_group_rules']:
516 if rule['security_group_id'] == sec_grp.id:
517 out.append(SecurityGroupRule(**rule))
521 def get_rule_by_id(neutron, sec_grp, rule_id):
523 Deletes a security group object from OpenStack
524 :param neutron: the client
525 :param sec_grp: the SNAPS SecurityGroup domain object
526 :param rule_id: the rule's ID
527 :param sec_grp: a SNAPS SecurityGroupRule domain object
529 rules = neutron.list_security_group_rules(
530 **{'security_group_id': sec_grp.id})
531 for rule in rules['security_group_rules']:
532 if rule['id'] == rule_id:
533 return SecurityGroupRule(**rule)
537 def get_external_networks(neutron):
539 Returns a list of external OpenStack network object/dict for all external
541 :param neutron: the client
542 :return: a list of external networks of Type SNAPS-OO domain class Network
545 for network in neutron.list_networks(
546 **{'router:external': True})['networks']:
547 out.append(Network(**network))
551 def get_floating_ips(neutron, ports=None):
553 Returns all of the floating IPs
554 When ports is not None, FIPs returned must be associated with one of the
555 ports in the list and a tuple 2 where the first element being the port's
556 name and the second being the FloatingIp SNAPS-OO domain object.
557 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
559 :param neutron: the Neutron client
560 :param ports: a list of SNAPS-OO Port objects to join
561 :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
562 is not None else a list of Port objects
565 fips = neutron.list_floatingips()
566 for fip in fips['floatingips']:
568 for port_name, port in ports:
569 if fip['port_id'] == port.id:
570 out.append((port.name, FloatingIp(
571 inst_id=fip['id'], ip=fip['floating_ip_address'])))
574 out.append(FloatingIp(inst_id=fip['id'],
575 ip=fip['floating_ip_address']))
580 def create_floating_ip(neutron, ext_net_name):
582 Returns the floating IP object that was created with this call
583 :param neutron: the Neutron client
584 :param ext_net_name: the name of the external network on which to apply the
586 :return: the SNAPS FloatingIp object
588 logger.info('Creating floating ip to external network - ' + ext_net_name)
589 ext_net = get_network(neutron, network_name=ext_net_name)
591 fip = neutron.create_floatingip(
593 {'floating_network_id': ext_net.id}})
595 return FloatingIp(inst_id=fip['floatingip']['id'],
596 ip=fip['floatingip']['floating_ip_address'])
598 raise NeutronException(
599 'Cannot create floating IP, external network not found')
602 def get_floating_ip(neutron, floating_ip):
604 Returns a floating IP object that should be identical to the floating_ip
606 :param neutron: the Neutron client
607 :param floating_ip: the SNAPS FloatingIp object
608 :return: hopefully the same floating IP object input
610 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
612 os_fip = __get_os_floating_ip(neutron, floating_ip)
615 inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
618 def __get_os_floating_ip(neutron, floating_ip):
620 Returns an OpenStack floating IP object
622 :param neutron: the Neutron client
623 :param floating_ip: the SNAPS FloatingIp object
624 :return: hopefully the same floating IP object input
626 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
628 fips = neutron.list_floatingips(ip=floating_ip.id)
630 for fip in fips['floatingips']:
631 if fip['id'] == floating_ip.id:
635 def delete_floating_ip(neutron, floating_ip):
637 Responsible for deleting a floating IP
638 :param neutron: the Neutron client
639 :param floating_ip: the SNAPS FloatingIp object
642 logger.debug('Attempting to delete existing floating ip with IP - %s',
644 return neutron.delete_floatingip(floating_ip.id)
647 class NeutronException(Exception):
649 Exception when calls to the Keystone client cannot be served properly