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 create_router(neutron, os_creds, router_settings):
192 Creates a router for OpenStack
193 :param neutron: the client
194 :param os_creds: the OpenStack credentials
195 :param router_settings: A dictionary containing the router configuration
196 and is responsible for creating the subnet request
198 :return: a SNAPS-OO Router domain object
201 json_body = router_settings.dict_for_neutron(neutron, os_creds)
202 logger.info('Creating router with name - ' + router_settings.name)
203 os_router = neutron.create_router(json_body)
204 return Router(**os_router['router'])
206 logger.error("Failed to create router.")
207 raise NeutronException('Failed to create router')
210 def delete_router(neutron, router):
212 Deletes a router for OpenStack
213 :param neutron: the client
214 :param router: a SNAPS-OO Router domain object
216 if neutron and router:
217 logger.info('Deleting router with name - ' + router.name)
218 neutron.delete_router(router=router.id)
221 def get_router_by_name(neutron, router_name):
223 Returns the first router object (dictionary) found with a given name
224 :param neutron: the client
225 :param router_name: the name of the network to retrieve
226 :return: a SNAPS-OO Router domain object
228 routers = neutron.list_routers(**{'name': router_name})
229 for router, routerInst in routers.items():
230 for inst in routerInst:
231 if inst.get('name') == router_name:
232 return Router(**inst)
236 def add_interface_router(neutron, router, subnet=None, port=None):
238 Adds an interface router for OpenStack for either a subnet or port.
239 Exception will be raised if requesting for both.
240 :param neutron: the client
241 :param router: the router object
242 :param subnet: the subnet object
243 :param port: the port object
244 :return: the interface router object
247 raise NeutronException(
248 'Cannot add interface to the router. Both subnet and '
249 'port were sent in. Either or please.')
251 if neutron and router and (router or subnet):
252 logger.info('Adding interface to router with name ' + router.name)
253 os_intf_router = neutron.add_interface_router(
254 router=router.id, body=__create_port_json_body(subnet, port))
255 return InterfaceRouter(**os_intf_router)
257 raise NeutronException(
258 'Unable to create interface router as neutron client,'
259 ' router or subnet were not created')
262 def remove_interface_router(neutron, router, subnet=None, port=None):
264 Removes an interface router for OpenStack
265 :param neutron: the client
266 :param router: the SNAPS-OO Router domain object
267 :param subnet: the subnet object (either subnet or port, not both)
268 :param port: the port object
272 logger.info('Removing router interface from router named ' +
274 neutron.remove_interface_router(
276 body=__create_port_json_body(subnet, port))
277 except NotFound as e:
278 logger.warning('Could not remove router interface. NotFound - %s',
282 logger.warning('Could not remove router interface, No router object')
285 def __create_port_json_body(subnet=None, port=None):
287 Returns the dictionary required for creating and deleting router
288 interfaces. Will only work on a subnet or port object. Will throw and
289 exception if parameters contain both or neither
290 :param subnet: the subnet object
291 :param port: the port object
295 raise NeutronException(
296 'Cannot create JSON body with both subnet and port')
297 if not subnet and not port:
298 raise NeutronException(
299 'Cannot create JSON body without subnet or port')
302 return {"subnet_id": subnet.id}
304 return {"port_id": port.id}
307 def create_port(neutron, os_creds, port_settings):
309 Creates a port for OpenStack
310 :param neutron: the client
311 :param os_creds: the OpenStack credentials
312 :param port_settings: the settings object for port configuration
313 :return: the SNAPS-OO Port domain object
315 json_body = port_settings.dict_for_neutron(neutron, os_creds)
316 logger.info('Creating port for network with name - %s',
317 port_settings.network_name)
318 os_port = neutron.create_port(body=json_body)['port']
319 return Port(name=os_port['name'], id=os_port['id'],
320 ips=os_port['fixed_ips'],
321 mac_address=os_port['mac_address'],
322 allowed_address_pairs=os_port['allowed_address_pairs'])
325 def delete_port(neutron, port):
327 Removes an OpenStack port
328 :param neutron: the client
329 :param port: the SNAPS-OO Port domain object
331 logger.info('Deleting port with name ' + port.name)
332 neutron.delete_port(port.id)
335 def get_port(neutron, port_settings=None, port_name=None):
337 Returns the first port object (dictionary) found for the given query
338 :param neutron: the client
339 :param port_settings: the PortSettings object used for generating the query
340 :param port_name: if port_settings is None, this name is the value to place
342 :return: a SNAPS-OO Port domain object
347 port_filter['name'] = port_settings.name
348 if port_settings.admin_state_up:
349 port_filter['admin_state_up'] = port_settings.admin_state_up
350 if port_settings.device_id:
351 port_filter['device_id'] = port_settings.device_id
352 if port_settings.mac_address:
353 port_filter['mac_address'] = port_settings.mac_address
355 port_filter['name'] = port_name
357 ports = neutron.list_ports(**port_filter)
358 for port in ports['ports']:
359 return Port(name=port['name'], id=port['id'],
360 ips=port['fixed_ips'], mac_address=port['mac_address'])
364 def create_security_group(neutron, keystone, sec_grp_settings):
366 Creates a security group object in OpenStack
367 :param neutron: the Neutron client
368 :param keystone: the Keystone client
369 :param sec_grp_settings: the security group settings
370 :return: a SNAPS-OO SecurityGroup domain object
372 logger.info('Creating security group with name - %s',
373 sec_grp_settings.name)
374 os_group = neutron.create_security_group(
375 sec_grp_settings.dict_for_neutron(keystone))
376 return SecurityGroup(**os_group['security_group'])
379 def delete_security_group(neutron, sec_grp):
381 Deletes a security group object from OpenStack
382 :param neutron: the client
383 :param sec_grp: the SNAPS SecurityGroup object to delete
385 logger.info('Deleting security group with name - %s', sec_grp.name)
386 neutron.delete_security_group(sec_grp.id)
389 def get_security_group(neutron, name, tenant_id=None):
391 Returns the first security group object of the given name else None
392 :param neutron: the client
393 :param name: the name of security group object to retrieve
394 :return: a SNAPS-OO SecurityGroup domain object or None if not found
396 logger.info('Retrieving security group with name - ' + name)
398 filter = {'name': name}
400 filter['tenant_id'] = tenant_id
401 groups = neutron.list_security_groups(**filter)
402 for group in groups['security_groups']:
403 if group['name'] == name:
404 return SecurityGroup(**group)
408 def get_security_group_by_id(neutron, sec_grp_id):
410 Returns the first security group object of the given name else None
411 :param neutron: the client
412 :param sec_grp_id: the id of the security group to retrieve
413 :return: a SNAPS-OO SecurityGroup domain object or None if not found
415 logger.info('Retrieving security group with ID - ' + sec_grp_id)
417 groups = neutron.list_security_groups(**{'id': sec_grp_id})
418 for group in groups['security_groups']:
419 if group['id'] == sec_grp_id:
420 return SecurityGroup(**group)
424 def create_security_group_rule(neutron, sec_grp_rule_settings):
426 Creates a security group object in OpenStack
427 :param neutron: the client
428 :param sec_grp_rule_settings: the security group rule settings
429 :return: a SNAPS-OO SecurityGroupRule domain object
431 logger.info('Creating security group to security group - %s',
432 sec_grp_rule_settings.sec_grp_name)
433 os_rule = neutron.create_security_group_rule(
434 sec_grp_rule_settings.dict_for_neutron(neutron))
435 return SecurityGroupRule(**os_rule['security_group_rule'])
438 def delete_security_group_rule(neutron, sec_grp_rule):
440 Deletes a security group object from OpenStack
441 :param neutron: the client
442 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
444 logger.info('Deleting security group rule with ID - %s',
446 neutron.delete_security_group_rule(sec_grp_rule.id)
449 def get_rules_by_security_group(neutron, sec_grp):
451 Retrieves all of the rules for a given security group
452 :param neutron: the client
453 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
455 logger.info('Retrieving security group rules associate with the '
456 'security group - %s', sec_grp.name)
458 rules = neutron.list_security_group_rules(
459 **{'security_group_id': sec_grp.id})
460 for rule in rules['security_group_rules']:
461 if rule['security_group_id'] == sec_grp.id:
462 out.append(SecurityGroupRule(**rule))
466 def get_rule_by_id(neutron, sec_grp, rule_id):
468 Deletes a security group object from OpenStack
469 :param neutron: the client
470 :param sec_grp: the SNAPS SecurityGroup domain object
471 :param rule_id: the rule's ID
472 :param sec_grp: a SNAPS SecurityGroupRule domain object
474 rules = neutron.list_security_group_rules(
475 **{'security_group_id': sec_grp.id})
476 for rule in rules['security_group_rules']:
477 if rule['id'] == rule_id:
478 return SecurityGroupRule(**rule)
482 def get_external_networks(neutron):
484 Returns a list of external OpenStack network object/dict for all external
486 :param neutron: the client
487 :return: a list of external networks of Type SNAPS-OO domain class Network
490 for network in neutron.list_networks(
491 **{'router:external': True})['networks']:
492 out.append(Network(**network))
496 def get_floating_ips(neutron, ports=None):
498 Returns all of the floating IPs
499 When ports is not None, FIPs returned must be associated with one of the
500 ports in the list and a tuple 2 where the first element being the port's
501 name and the second being the FloatingIp SNAPS-OO domain object.
502 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
504 :param neutron: the Neutron client
505 :param ports: a list of SNAPS-OO Port objects to join
506 :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
507 is not None else a list of Port objects
510 fips = neutron.list_floatingips()
511 for fip in fips['floatingips']:
513 for port_name, port in ports:
514 if fip['port_id'] == port.id:
515 out.append((port.name, FloatingIp(
516 inst_id=fip['id'], ip=fip['floating_ip_address'])))
519 out.append(FloatingIp(inst_id=fip['id'],
520 ip=fip['floating_ip_address']))
525 def create_floating_ip(neutron, ext_net_name):
527 Returns the floating IP object that was created with this call
528 :param neutron: the Neutron client
529 :param ext_net_name: the name of the external network on which to apply the
531 :return: the SNAPS FloatingIp object
533 logger.info('Creating floating ip to external network - ' + ext_net_name)
534 ext_net = get_network(neutron, network_name=ext_net_name)
536 fip = neutron.create_floatingip(
538 {'floating_network_id': ext_net.id}})
540 return FloatingIp(inst_id=fip['floatingip']['id'],
541 ip=fip['floatingip']['floating_ip_address'])
543 raise NeutronException(
544 'Cannot create floating IP, external network not found')
547 def get_floating_ip(neutron, floating_ip):
549 Returns a floating IP object that should be identical to the floating_ip
551 :param neutron: the Neutron client
552 :param floating_ip: the SNAPS FloatingIp object
553 :return: hopefully the same floating IP object input
555 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
557 os_fip = __get_os_floating_ip(neutron, floating_ip)
560 inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
563 def __get_os_floating_ip(neutron, floating_ip):
565 Returns an OpenStack floating IP object
567 :param neutron: the Neutron client
568 :param floating_ip: the SNAPS FloatingIp object
569 :return: hopefully the same floating IP object input
571 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
573 fips = neutron.list_floatingips(ip=floating_ip.id)
575 for fip in fips['floatingips']:
576 if fip['id'] == floating_ip.id:
580 def delete_floating_ip(neutron, floating_ip):
582 Responsible for deleting a floating IP
583 :param neutron: the Neutron client
584 :param floating_ip: the SNAPS FloatingIp object
587 logger.debug('Attempting to delete existing floating ip with IP - %s',
589 return neutron.delete_floatingip(floating_ip.id)
592 class NeutronException(Exception):
594 Exception when calls to the Keystone client cannot be served properly