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_name, project_id=None):
79 Returns an object (dictionary) of the first network found with a given name
80 and project_id (if included)
81 :param neutron: the client
82 :param network_name: the name of the network to retrieve
83 :param project_id: the id of the network's project
84 :return: a SNAPS-OO Network domain object
88 net_filter['name'] = network_name
90 net_filter['project_id'] = project_id
92 networks = neutron.list_networks(**net_filter)
93 for network, netInsts in networks.items():
95 if inst.get('name') == network_name:
96 return Network(**inst)
100 def get_network_by_id(neutron, network_id):
102 Returns the network object (dictionary) with the given ID
103 :param neutron: the client
104 :param network_id: the id of the network to retrieve
105 :return: a SNAPS-OO Network domain object
107 networks = neutron.list_networks(**{'id': network_id})
108 for network, netInsts in networks.items():
109 for inst in netInsts:
110 if inst.get('id') == network_id:
111 return Network(**inst)
115 def create_subnet(neutron, subnet_settings, os_creds, network=None):
117 Creates a network subnet for OpenStack
118 :param neutron: the client
119 :param network: the network object
120 :param subnet_settings: A dictionary containing the subnet configuration
121 and is responsible for creating the subnet request
123 :param os_creds: the OpenStack credentials
124 :return: a SNAPS-OO Subnet domain object
126 if neutron and network and subnet_settings:
127 json_body = {'subnets': [subnet_settings.dict_for_neutron(
128 os_creds, network=network)]}
129 logger.info('Creating subnet with name ' + subnet_settings.name)
130 subnets = neutron.create_subnet(body=json_body)
131 return Subnet(**subnets['subnets'][0])
133 raise NeutronException('Failed to create subnet')
136 def delete_subnet(neutron, subnet):
138 Deletes a network subnet for OpenStack
139 :param neutron: the client
140 :param subnet: a SNAPS-OO Subnet domain object
142 if neutron and subnet:
143 logger.info('Deleting subnet with name ' + subnet.name)
144 neutron.delete_subnet(subnet.id)
147 def get_subnet_by_name(neutron, subnet_name):
149 Returns the first subnet object (dictionary) found with a given name
150 :param neutron: the client
151 :param subnet_name: the name of the network to retrieve
152 :return: a SNAPS-OO Subnet domain object
154 subnets = neutron.list_subnets(**{'name': subnet_name})
155 for subnet, subnetInst in subnets.items():
156 for inst in subnetInst:
157 if inst['name'] == subnet_name:
158 return Subnet(**inst)
162 def create_router(neutron, os_creds, router_settings):
164 Creates a router for OpenStack
165 :param neutron: the client
166 :param os_creds: the OpenStack credentials
167 :param router_settings: A dictionary containing the router configuration
168 and is responsible for creating the subnet request
170 :return: a SNAPS-OO Router domain object
173 json_body = router_settings.dict_for_neutron(neutron, os_creds)
174 logger.info('Creating router with name - ' + router_settings.name)
175 os_router = neutron.create_router(json_body)
176 return Router(**os_router['router'])
178 logger.error("Failed to create router.")
179 raise NeutronException('Failed to create router')
182 def delete_router(neutron, router):
184 Deletes a router for OpenStack
185 :param neutron: the client
186 :param router: a SNAPS-OO Router domain object
188 if neutron and router:
189 logger.info('Deleting router with name - ' + router.name)
190 neutron.delete_router(router=router.id)
193 def get_router_by_name(neutron, router_name):
195 Returns the first router object (dictionary) found with a given name
196 :param neutron: the client
197 :param router_name: the name of the network to retrieve
198 :return: a SNAPS-OO Router domain object
200 routers = neutron.list_routers(**{'name': router_name})
201 for router, routerInst in routers.items():
202 for inst in routerInst:
203 if inst.get('name') == router_name:
204 return Router(**inst)
208 def add_interface_router(neutron, router, subnet=None, port=None):
210 Adds an interface router for OpenStack for either a subnet or port.
211 Exception will be raised if requesting for both.
212 :param neutron: the client
213 :param router: the router object
214 :param subnet: the subnet object
215 :param port: the port object
216 :return: the interface router object
219 raise NeutronException(
220 'Cannot add interface to the router. Both subnet and '
221 'port were sent in. Either or please.')
223 if neutron and router and (router or subnet):
224 logger.info('Adding interface to router with name ' + router.name)
225 os_intf_router = neutron.add_interface_router(
226 router=router.id, body=__create_port_json_body(subnet, port))
227 return InterfaceRouter(**os_intf_router)
229 raise NeutronException(
230 'Unable to create interface router as neutron client,'
231 ' router or subnet were not created')
234 def remove_interface_router(neutron, router, subnet=None, port=None):
236 Removes an interface router for OpenStack
237 :param neutron: the client
238 :param router: the SNAPS-OO Router domain object
239 :param subnet: the subnet object (either subnet or port, not both)
240 :param port: the port object
244 logger.info('Removing router interface from router named ' +
246 neutron.remove_interface_router(
248 body=__create_port_json_body(subnet, port))
249 except NotFound as e:
250 logger.warning('Could not remove router interface. NotFound - %s',
254 logger.warning('Could not remove router interface, No router object')
257 def __create_port_json_body(subnet=None, port=None):
259 Returns the dictionary required for creating and deleting router
260 interfaces. Will only work on a subnet or port object. Will throw and
261 exception if parameters contain both or neither
262 :param subnet: the subnet object
263 :param port: the port object
267 raise NeutronException(
268 'Cannot create JSON body with both subnet and port')
269 if not subnet and not port:
270 raise NeutronException(
271 'Cannot create JSON body without subnet or port')
274 return {"subnet_id": subnet.id}
276 return {"port_id": port.id}
279 def create_port(neutron, os_creds, port_settings):
281 Creates a port for OpenStack
282 :param neutron: the client
283 :param os_creds: the OpenStack credentials
284 :param port_settings: the settings object for port configuration
285 :return: the SNAPS-OO Port domain object
287 json_body = port_settings.dict_for_neutron(neutron, os_creds)
288 logger.info('Creating port for network with name - %s',
289 port_settings.network_name)
290 os_port = neutron.create_port(body=json_body)['port']
291 return Port(name=os_port['name'], id=os_port['id'],
292 ips=os_port['fixed_ips'],
293 mac_address=os_port['mac_address'],
294 allowed_address_pairs=os_port['allowed_address_pairs'])
297 def delete_port(neutron, port):
299 Removes an OpenStack port
300 :param neutron: the client
301 :param port: the SNAPS-OO Port domain object
303 logger.info('Deleting port with name ' + port.name)
304 neutron.delete_port(port.id)
307 def get_port_by_name(neutron, port_name):
309 Returns the first port object (dictionary) found with a given name
310 :param neutron: the client
311 :param port_name: the name of the port to retrieve
312 :return: a SNAPS-OO Port domain object
314 ports = neutron.list_ports(**{'name': port_name})
315 for port in ports['ports']:
316 if port['name'] == port_name:
317 return Port(name=port['name'], id=port['id'],
318 ips=port['fixed_ips'], mac_address=port['mac_address'])
322 def create_security_group(neutron, keystone, sec_grp_settings):
324 Creates a security group object in OpenStack
325 :param neutron: the Neutron client
326 :param keystone: the Keystone client
327 :param sec_grp_settings: the security group settings
328 :return: a SNAPS-OO SecurityGroup domain object
330 logger.info('Creating security group with name - %s',
331 sec_grp_settings.name)
332 os_group = neutron.create_security_group(
333 sec_grp_settings.dict_for_neutron(keystone))
334 return SecurityGroup(**os_group['security_group'])
337 def delete_security_group(neutron, sec_grp):
339 Deletes a security group object from OpenStack
340 :param neutron: the client
341 :param sec_grp: the SNAPS SecurityGroup object to delete
343 logger.info('Deleting security group with name - %s', sec_grp.name)
344 neutron.delete_security_group(sec_grp.id)
347 def get_security_group(neutron, name):
349 Returns the first security group object of the given name else None
350 :param neutron: the client
351 :param name: the name of security group object to retrieve
352 :return: a SNAPS-OO SecurityGroup domain object or None if not found
354 logger.info('Retrieving security group with name - ' + name)
356 groups = neutron.list_security_groups(**{'name': name})
357 for group in groups['security_groups']:
358 if group['name'] == name:
359 return SecurityGroup(**group)
363 def get_security_group_by_id(neutron, sec_grp_id):
365 Returns the first security group object of the given name else None
366 :param neutron: the client
367 :param sec_grp_id: the id of the security group to retrieve
368 :return: a SNAPS-OO SecurityGroup domain object or None if not found
370 logger.info('Retrieving security group with ID - ' + sec_grp_id)
372 groups = neutron.list_security_groups(**{'id': sec_grp_id})
373 for group in groups['security_groups']:
374 if group['id'] == sec_grp_id:
375 return SecurityGroup(**group)
379 def create_security_group_rule(neutron, sec_grp_rule_settings):
381 Creates a security group object in OpenStack
382 :param neutron: the client
383 :param sec_grp_rule_settings: the security group rule settings
384 :return: a SNAPS-OO SecurityGroupRule domain object
386 logger.info('Creating security group to security group - %s',
387 sec_grp_rule_settings.sec_grp_name)
388 os_rule = neutron.create_security_group_rule(
389 sec_grp_rule_settings.dict_for_neutron(neutron))
390 return SecurityGroupRule(**os_rule['security_group_rule'])
393 def delete_security_group_rule(neutron, sec_grp_rule):
395 Deletes a security group object from OpenStack
396 :param neutron: the client
397 :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
399 logger.info('Deleting security group rule with ID - %s',
401 neutron.delete_security_group_rule(sec_grp_rule.id)
404 def get_rules_by_security_group(neutron, sec_grp):
406 Retrieves all of the rules for a given security group
407 :param neutron: the client
408 :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
410 logger.info('Retrieving security group rules associate with the '
411 'security group - %s', sec_grp.name)
413 rules = neutron.list_security_group_rules(
414 **{'security_group_id': sec_grp.id})
415 for rule in rules['security_group_rules']:
416 if rule['security_group_id'] == sec_grp.id:
417 out.append(SecurityGroupRule(**rule))
421 def get_rule_by_id(neutron, sec_grp, rule_id):
423 Deletes a security group object from OpenStack
424 :param neutron: the client
425 :param sec_grp: the SNAPS SecurityGroup domain object
426 :param rule_id: the rule's ID
427 :param sec_grp: a SNAPS SecurityGroupRule domain object
429 rules = neutron.list_security_group_rules(
430 **{'security_group_id': sec_grp.id})
431 for rule in rules['security_group_rules']:
432 if rule['id'] == rule_id:
433 return SecurityGroupRule(**rule)
437 def get_external_networks(neutron):
439 Returns a list of external OpenStack network object/dict for all external
441 :param neutron: the client
442 :return: a list of external networks of Type SNAPS-OO domain class Network
445 for network in neutron.list_networks(
446 **{'router:external': True})['networks']:
447 out.append(Network(**network))
451 def get_floating_ips(neutron, ports=None):
453 Returns all of the floating IPs
454 When ports is not None, FIPs returned must be associated with one of the
455 ports in the list and a tuple 2 where the first element being the port's
456 name and the second being the FloatingIp SNAPS-OO domain object.
457 When ports is None, all known FloatingIp SNAPS-OO domain objects will be
459 :param neutron: the Neutron client
460 :param ports: a list of SNAPS-OO Port objects to join
461 :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
462 is not None else a list of Port objects
465 fips = neutron.list_floatingips()
466 for fip in fips['floatingips']:
468 for port_name, port in ports:
469 if fip['port_id'] == port.id:
470 out.append((port.name, FloatingIp(
471 inst_id=fip['id'], ip=fip['floating_ip_address'])))
474 out.append(FloatingIp(inst_id=fip['id'],
475 ip=fip['floating_ip_address']))
480 def create_floating_ip(neutron, ext_net_name):
482 Returns the floating IP object that was created with this call
483 :param neutron: the Neutron client
484 :param ext_net_name: the name of the external network on which to apply the
486 :return: the SNAPS FloatingIp object
488 logger.info('Creating floating ip to external network - ' + ext_net_name)
489 ext_net = get_network(neutron, ext_net_name)
491 fip = neutron.create_floatingip(
493 {'floating_network_id': ext_net.id}})
495 return FloatingIp(inst_id=fip['floatingip']['id'],
496 ip=fip['floatingip']['floating_ip_address'])
498 raise NeutronException(
499 'Cannot create floating IP, external network not found')
502 def get_floating_ip(neutron, floating_ip):
504 Returns a floating IP object that should be identical to the floating_ip
506 :param neutron: the Neutron client
507 :param floating_ip: the SNAPS FloatingIp object
508 :return: hopefully the same floating IP object input
510 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
512 os_fip = __get_os_floating_ip(neutron, floating_ip)
515 inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
518 def __get_os_floating_ip(neutron, floating_ip):
520 Returns an OpenStack floating IP object
522 :param neutron: the Neutron client
523 :param floating_ip: the SNAPS FloatingIp object
524 :return: hopefully the same floating IP object input
526 logger.debug('Attempting to retrieve existing floating ip with IP - %s',
528 fips = neutron.list_floatingips(ip=floating_ip.id)
530 for fip in fips['floatingips']:
531 if fip['id'] == floating_ip.id:
535 def delete_floating_ip(neutron, floating_ip):
537 Responsible for deleting a floating IP
538 :param neutron: the Neutron client
539 :param floating_ip: the SNAPS FloatingIp object
542 logger.debug('Attempting to delete existing floating ip with IP - %s',
544 return neutron.delete_floatingip(floating_ip.id)
547 class NeutronException(Exception):
549 Exception when calls to the Keystone client cannot be served properly