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(neutron, router_settings=None, router_name=None):
223 Returns the first router object (dictionary) found the given the settings
224 values if not None, else finds the first with the value of the router_name
226 :param neutron: the client
227 :param router_settings: the RouterSettings object
228 :param router_name: the name of the network to retrieve
229 :return: a SNAPS-OO Router domain object
231 router_filter = dict()
233 router_filter['name'] = router_settings.name
234 if router_settings.admin_state_up is not None:
235 router_filter['admin_state_up'] = router_settings.admin_state_up
237 router_filter['name'] = router_name
241 routers = neutron.list_routers(**router_filter)
242 for routerInst in routers['routers']:
243 return Router(**routerInst)
247 def add_interface_router(neutron, router, subnet=None, port=None):
249 Adds an interface router for OpenStack for either a subnet or port.
250 Exception will be raised if requesting for both.
251 :param neutron: the client
252 :param router: the router object
253 :param subnet: the subnet object
254 :param port: the port object
255 :return: the interface router object
258 raise NeutronException(
259 'Cannot add interface to the router. Both subnet and '
260 'port were sent in. Either or please.')
262 if neutron and router and (router or subnet):
263 logger.info('Adding interface to router with name ' + router.name)
264 os_intf_router = neutron.add_interface_router(
265 router=router.id, body=__create_port_json_body(subnet, port))
266 return InterfaceRouter(**os_intf_router)
268 raise NeutronException(
269 'Unable to create interface router as neutron client,'
270 ' router or subnet were not created')
273 def remove_interface_router(neutron, router, subnet=None, port=None):
275 Removes an interface router for OpenStack
276 :param neutron: the client
277 :param router: the SNAPS-OO Router domain object
278 :param subnet: the subnet object (either subnet or port, not both)
279 :param port: the port object
283 logger.info('Removing router interface from router named ' +
285 neutron.remove_interface_router(
287 body=__create_port_json_body(subnet, port))
288 except NotFound as e:
289 logger.warning('Could not remove router interface. NotFound - %s',
293 logger.warning('Could not remove router interface, No router object')
296 def __create_port_json_body(subnet=None, port=None):
298 Returns the dictionary required for creating and deleting router
299 interfaces. Will only work on a subnet or port object. Will throw and
300 exception if parameters contain both or neither
301 :param subnet: the subnet object
302 :param port: the port object
306 raise NeutronException(
307 'Cannot create JSON body with both subnet and port')
308 if not subnet and not port:
309 raise NeutronException(
310 'Cannot create JSON body without subnet or port')
313 return {"subnet_id": subnet.id}
315 return {"port_id": port.id}
318 def create_port(neutron, os_creds, port_settings):
320 Creates a port for OpenStack
321 :param neutron: the client
322 :param os_creds: the OpenStack credentials
323 :param port_settings: the settings object for port configuration
324 :return: the SNAPS-OO Port domain object
326 json_body = port_settings.dict_for_neutron(neutron, os_creds)
327 logger.info('Creating port for network with name - %s',
328 port_settings.network_name)
329 os_port = neutron.create_port(body=json_body)['port']
330 return Port(name=os_port['name'], id=os_port['id'],
331 ips=os_port['fixed_ips'],
332 mac_address=os_port['mac_address'],
333 allowed_address_pairs=os_port['allowed_address_pairs'])
336 def delete_port(neutron, port):
338 Removes an OpenStack port
339 :param neutron: the client
340 :param port: the SNAPS-OO Port domain object
342 logger.info('Deleting port with name ' + port.name)
343 neutron.delete_port(port.id)
346 def get_port(neutron, port_settings=None, port_name=None):
348 Returns the first port object (dictionary) found for the given query
349 :param neutron: the client
350 :param port_settings: the PortSettings object used for generating the query
351 :param port_name: if port_settings is None, this name is the value to place
353 :return: a SNAPS-OO Port domain object
358 port_filter['name'] = port_settings.name
359 if port_settings.admin_state_up:
360 port_filter['admin_state_up'] = port_settings.admin_state_up
361 if port_settings.device_id:
362 port_filter['device_id'] = port_settings.device_id
363 if port_settings.mac_address:
364 port_filter['mac_address'] = port_settings.mac_address
366 port_filter['name'] = port_name
368 ports = neutron.list_ports(**port_filter)
369 for port in ports['ports']:
370 return Port(name=port['name'], id=port['id'],
371 ips=port['fixed_ips'], mac_address=port['mac_address'])
375 def create_security_group(neutron, keystone, sec_grp_settings):
377 Creates a security group object in OpenStack
378 :param neutron: the Neutron client
379 :param keystone: the Keystone client
380 :param sec_grp_settings: the security group settings
381 :return: a SNAPS-OO SecurityGroup domain object
383 logger.info('Creating security group with name - %s',
384 sec_grp_settings.name)
385 os_group = neutron.create_security_group(
386 sec_grp_settings.dict_for_neutron(keystone))
387 return SecurityGroup(**os_group['security_group'])
390 def delete_security_group(neutron, sec_grp):
392 Deletes a security group object from OpenStack
393 :param neutron: the client
394 :param sec_grp: the SNAPS SecurityGroup object to delete
396 logger.info('Deleting security group with name - %s', sec_grp.name)
397 neutron.delete_security_group(sec_grp.id)
400 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
403 Returns the first security group for a given query. The query gets built
404 from the sec_grp_settings parameter if not None, else only the name of
405 the security group will be used, else if the query parameters are None then
406 None will be returned
407 :param neutron: the client
408 :param sec_grp_settings: an instance of SecurityGroupSettings config object
409 :param sec_grp_name: the name of security group object to retrieve
410 :param project_id: the ID of the project/tentant object that owns the
411 secuity group to retrieve
412 :return: a SNAPS-OO SecurityGroup domain object or None if not found
415 sec_grp_filter = dict()
417 sec_grp_filter['tenant_id'] = project_id
420 sec_grp_filter['name'] = sec_grp_settings.name
422 if sec_grp_settings.description:
423 sec_grp_filter['description'] = sec_grp_settings.description
425 sec_grp_filter['name'] = sec_grp_name
429 groups = neutron.list_security_groups(**sec_grp_filter)
430 for group in groups['security_groups']:
431 return SecurityGroup(**group)
434 def get_security_group_by_id(neutron, sec_grp_id):
436 Returns the first security group object of the given name else None
437 :param neutron: the client
438 :param sec_grp_id: the id of the security group to retrieve
439 :return: a SNAPS-OO SecurityGroup domain object or None if not found
441 logger.info('Retrieving security group with ID - ' + sec_grp_id)
443 groups = neutron.list_security_groups(**{'id': sec_grp_id})
444 for group in groups['security_groups']:
445 if group['id'] == sec_grp_id:
446 return SecurityGroup(**group)
450 def create_security_group_rule(neutron, sec_grp_rule_settings):
452 Creates a security group object in OpenStack
453 :param neutron: the client
454 :param sec_grp_rule_settings: the security group rule settings
455 :return: a SNAPS-OO SecurityGroupRule domain object
457 logger.info('Creating security group to security group - %s',
458 sec_grp_rule_settings.sec_grp_name)
459 os_rule = neutron.create_security_group_rule(
460 sec_grp_rule_settings.dict_for_neutron(neutron))
461 return SecurityGroupRule(**os_rule['security_group_rule'])
464 def delete_security_group_rule(neutron, sec_grp_rule):
466 Deletes a security group object from OpenStack
467 :param neutron: the client
468 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
470 logger.info('Deleting security group rule with ID - %s',
472 neutron.delete_security_group_rule(sec_grp_rule.id)
475 def get_rules_by_security_group(neutron, sec_grp):
477 Retrieves all of the rules for a given security group
478 :param neutron: the client
479 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
481 logger.info('Retrieving security group rules associate with the '
482 'security group - %s', sec_grp.name)
484 rules = neutron.list_security_group_rules(
485 **{'security_group_id': sec_grp.id})
486 for rule in rules['security_group_rules']:
487 if rule['security_group_id'] == sec_grp.id:
488 out.append(SecurityGroupRule(**rule))
492 def get_rule_by_id(neutron, sec_grp, rule_id):
494 Deletes a security group object from OpenStack
495 :param neutron: the client
496 :param sec_grp: the SNAPS SecurityGroup domain object
497 :param rule_id: the rule's ID
498 :param sec_grp: a SNAPS SecurityGroupRule domain object
500 rules = neutron.list_security_group_rules(
501 **{'security_group_id': sec_grp.id})
502 for rule in rules['security_group_rules']:
503 if rule['id'] == rule_id:
504 return SecurityGroupRule(**rule)
508 def get_external_networks(neutron):
510 Returns a list of external OpenStack network object/dict for all external
512 :param neutron: the client
513 :return: a list of external networks of Type SNAPS-OO domain class Network
516 for network in neutron.list_networks(
517 **{'router:external': True})['networks']:
518 out.append(Network(**network))
522 def get_floating_ips(neutron, ports=None):
524 Returns all of the floating IPs
525 When ports is not None, FIPs returned must be associated with one of the
526 ports in the list and a tuple 2 where the first element being the port's
527 name and the second being the FloatingIp SNAPS-OO domain object.
528 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
530 :param neutron: the Neutron client
531 :param ports: a list of SNAPS-OO Port objects to join
532 :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
533 is not None else a list of Port objects
536 fips = neutron.list_floatingips()
537 for fip in fips['floatingips']:
539 for port_name, port in ports:
540 if fip['port_id'] == port.id:
541 out.append((port.name, FloatingIp(
542 inst_id=fip['id'], ip=fip['floating_ip_address'])))
545 out.append(FloatingIp(inst_id=fip['id'],
546 ip=fip['floating_ip_address']))
551 def create_floating_ip(neutron, ext_net_name):
553 Returns the floating IP object that was created with this call
554 :param neutron: the Neutron client
555 :param ext_net_name: the name of the external network on which to apply the
557 :return: the SNAPS FloatingIp object
559 logger.info('Creating floating ip to external network - ' + ext_net_name)
560 ext_net = get_network(neutron, network_name=ext_net_name)
562 fip = neutron.create_floatingip(
564 {'floating_network_id': ext_net.id}})
566 return FloatingIp(inst_id=fip['floatingip']['id'],
567 ip=fip['floatingip']['floating_ip_address'])
569 raise NeutronException(
570 'Cannot create floating IP, external network not found')
573 def get_floating_ip(neutron, floating_ip):
575 Returns a floating IP object that should be identical to the floating_ip
577 :param neutron: the Neutron client
578 :param floating_ip: the SNAPS FloatingIp object
579 :return: hopefully the same floating IP object input
581 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
583 os_fip = __get_os_floating_ip(neutron, floating_ip)
586 inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
589 def __get_os_floating_ip(neutron, floating_ip):
591 Returns an OpenStack floating IP object
593 :param neutron: the Neutron client
594 :param floating_ip: the SNAPS FloatingIp object
595 :return: hopefully the same floating IP object input
597 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
599 fips = neutron.list_floatingips(ip=floating_ip.id)
601 for fip in fips['floatingips']:
602 if fip['id'] == floating_ip.id:
606 def delete_floating_ip(neutron, floating_ip):
608 Responsible for deleting a floating IP
609 :param neutron: the Neutron client
610 :param floating_ip: the SNAPS FloatingIp object
613 logger.debug('Attempting to delete existing floating ip with IP - %s',
615 return neutron.delete_floatingip(floating_ip.id)
618 class NeutronException(Exception):
620 Exception when calls to the Keystone client cannot be served properly