4bc1be2d18415dd098313adca4ec633f7c9278ac
[snaps.git] / snaps / openstack / utils / neutron_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 import logging
16
17 from neutronclient.common.exceptions import NotFound
18 from neutronclient.neutron.client import Client
19
20 from snaps.domain.network import (
21     Port, SecurityGroup, SecurityGroupRule, Router, InterfaceRouter, Subnet,
22     Network)
23 from snaps.domain.vm_inst import FloatingIp
24 from snaps.openstack.utils import keystone_utils
25
26 __author__ = 'spisarski'
27
28 logger = logging.getLogger('neutron_utils')
29
30 """
31 Utilities for basic neutron API calls
32 """
33
34
35 def neutron_client(os_creds):
36     """
37     Instantiates and returns a client for communications with OpenStack's
38     Neutron server
39     :param os_creds: the credentials for connecting to the OpenStack remote API
40     :return: the client object
41     """
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)
45
46
47 def create_network(neutron, os_creds, network_settings):
48     """
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
54                             request JSON body
55     :return: a SNAPS-OO Network domain object
56     """
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'])
62     else:
63         raise NeutronException('Failded to create network')
64
65
66 def delete_network(neutron, network):
67     """
68     Deletes a network for OpenStack
69     :param neutron: the client
70     :param network: a SNAPS-OO Network domain object
71     """
72     if neutron and network:
73         logger.info('Deleting network with name ' + network.name)
74         neutron.delete_network(network.id)
75
76
77 def get_network(neutron, network_settings=None, network_name=None,
78                 project_id=None):
79     """
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
89     """
90     net_filter = dict()
91     if network_settings:
92         net_filter['name'] = network_settings.name
93     elif network_name:
94         net_filter['name'] = network_name
95
96     if project_id:
97         net_filter['project_id'] = project_id
98
99     networks = neutron.list_networks(**net_filter)
100     for network, netInsts in networks.items():
101         for inst in netInsts:
102             return Network(**inst)
103
104
105 def get_network_by_id(neutron, network_id):
106     """
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
111     """
112     networks = neutron.list_networks(**{'id': network_id})
113     for network in networks['networks']:
114         if network['id'] == network_id:
115             return Network(**network)
116
117
118 def create_subnet(neutron, subnet_settings, os_creds, network=None):
119     """
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
125                             JSON body
126     :param os_creds: the OpenStack credentials
127     :return: a SNAPS-OO Subnet domain object
128     """
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])
135     else:
136         raise NeutronException('Failed to create subnet')
137
138
139 def delete_subnet(neutron, subnet):
140     """
141     Deletes a network subnet for OpenStack
142     :param neutron: the client
143     :param subnet: a SNAPS-OO Subnet domain object
144     """
145     if neutron and subnet:
146         logger.info('Deleting subnet with name ' + subnet.name)
147         neutron.delete_subnet(subnet.id)
148
149
150 def get_subnet(neutron, subnet_settings=None, subnet_name=None):
151     """
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
158     """
159     sub_filter = dict()
160     if subnet_settings:
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
165
166         if subnet_settings.enable_dhcp is not None:
167             sub_filter['enable_dhcp'] = subnet_settings.enable_dhcp
168
169         if subnet_settings.destination:
170             sub_filter['destination'] = subnet_settings.destination
171
172         if subnet_settings.nexthop:
173             sub_filter['nexthop'] = subnet_settings.nexthop
174
175         if subnet_settings.ipv6_ra_mode:
176             sub_filter['ipv6_ra_mode'] = subnet_settings.ipv6_ra_mode
177
178         if subnet_settings.ipv6_address_mode:
179             sub_filter['ipv6_address_mode'] = subnet_settings.ipv6_address_mode
180     elif subnet_name:
181         sub_filter['name'] = subnet_name
182     else:
183         return None
184
185     subnets = neutron.list_subnets(**sub_filter)
186     for subnet in subnets['subnets']:
187         return Subnet(**subnet)
188
189
190 def create_router(neutron, os_creds, router_settings):
191     """
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
197                             JSON body
198     :return: a SNAPS-OO Router domain object
199     """
200     if neutron:
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'])
205     else:
206         logger.error("Failed to create router.")
207         raise NeutronException('Failed to create router')
208
209
210 def delete_router(neutron, router):
211     """
212     Deletes a router for OpenStack
213     :param neutron: the client
214     :param router: a SNAPS-OO Router domain object
215     """
216     if neutron and router:
217         logger.info('Deleting router with name - ' + router.name)
218         neutron.delete_router(router=router.id)
219
220
221 def get_router_by_name(neutron, router_name):
222     """
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
227     """
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)
233     return None
234
235
236 def add_interface_router(neutron, router, subnet=None, port=None):
237     """
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
245     """
246     if subnet and port:
247         raise NeutronException(
248             'Cannot add interface to the router. Both subnet and '
249             'port were sent in. Either or please.')
250
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)
256     else:
257         raise NeutronException(
258             'Unable to create interface router as neutron client,'
259             ' router or subnet were not created')
260
261
262 def remove_interface_router(neutron, router, subnet=None, port=None):
263     """
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
269     """
270     if router:
271         try:
272             logger.info('Removing router interface from router named ' +
273                         router.name)
274             neutron.remove_interface_router(
275                 router=router.id,
276                 body=__create_port_json_body(subnet, port))
277         except NotFound as e:
278             logger.warning('Could not remove router interface. NotFound - %s',
279                            e)
280             pass
281     else:
282         logger.warning('Could not remove router interface, No router object')
283
284
285 def __create_port_json_body(subnet=None, port=None):
286     """
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
292     :return: the dict
293     """
294     if subnet and port:
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')
300
301     if subnet:
302         return {"subnet_id": subnet.id}
303     else:
304         return {"port_id": port.id}
305
306
307 def create_port(neutron, os_creds, port_settings):
308     """
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
314     """
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'])
323
324
325 def delete_port(neutron, port):
326     """
327     Removes an OpenStack port
328     :param neutron: the client
329     :param port: the SNAPS-OO Port domain object
330     """
331     logger.info('Deleting port with name ' + port.name)
332     neutron.delete_port(port.id)
333
334
335 def get_port(neutron, port_settings=None, port_name=None):
336     """
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
341                       into the query
342     :return: a SNAPS-OO Port domain object
343     """
344     port_filter = dict()
345
346     if port_settings:
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
354     elif port_name:
355         port_filter['name'] = port_name
356
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'])
361     return None
362
363
364 def create_security_group(neutron, keystone, sec_grp_settings):
365     """
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
371     """
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'])
377
378
379 def delete_security_group(neutron, sec_grp):
380     """
381     Deletes a security group object from OpenStack
382     :param neutron: the client
383     :param sec_grp: the SNAPS SecurityGroup object to delete
384     """
385     logger.info('Deleting security group with name - %s', sec_grp.name)
386     neutron.delete_security_group(sec_grp.id)
387
388
389 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
390                        project_id=None):
391     """
392     Returns the first security group for a given query. The query gets built
393     from the sec_grp_settings parameter if not None, else only the name of
394     the security group will be used, else if the query parameters are None then
395     None will be returned
396     :param neutron: the client
397     :param sec_grp_settings: an instance of SecurityGroupSettings config object
398     :param sec_grp_name: the name of security group object to retrieve
399     :param project_id: the ID of the project/tentant object that owns the
400                        secuity group to retrieve
401     :return: a SNAPS-OO SecurityGroup domain object or None if not found
402     """
403
404     sec_grp_filter = dict()
405     if project_id:
406         sec_grp_filter['tenant_id'] = project_id
407
408     if sec_grp_settings:
409         sec_grp_filter['name'] = sec_grp_settings.name
410
411         if sec_grp_settings.description:
412             sec_grp_filter['description'] = sec_grp_settings.description
413     elif sec_grp_name:
414         sec_grp_filter['name'] = sec_grp_name
415     else:
416         return None
417
418     groups = neutron.list_security_groups(**sec_grp_filter)
419     for group in groups['security_groups']:
420         return SecurityGroup(**group)
421
422
423 def get_security_group_by_id(neutron, sec_grp_id):
424     """
425     Returns the first security group object of the given name else None
426     :param neutron: the client
427     :param sec_grp_id: the id of the security group to retrieve
428     :return: a SNAPS-OO SecurityGroup domain object or None if not found
429     """
430     logger.info('Retrieving security group with ID - ' + sec_grp_id)
431
432     groups = neutron.list_security_groups(**{'id': sec_grp_id})
433     for group in groups['security_groups']:
434         if group['id'] == sec_grp_id:
435             return SecurityGroup(**group)
436     return None
437
438
439 def create_security_group_rule(neutron, sec_grp_rule_settings):
440     """
441     Creates a security group object in OpenStack
442     :param neutron: the client
443     :param sec_grp_rule_settings: the security group rule settings
444     :return: a SNAPS-OO SecurityGroupRule domain object
445     """
446     logger.info('Creating security group to security group - %s',
447                 sec_grp_rule_settings.sec_grp_name)
448     os_rule = neutron.create_security_group_rule(
449         sec_grp_rule_settings.dict_for_neutron(neutron))
450     return SecurityGroupRule(**os_rule['security_group_rule'])
451
452
453 def delete_security_group_rule(neutron, sec_grp_rule):
454     """
455     Deletes a security group object from OpenStack
456     :param neutron: the client
457     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
458     """
459     logger.info('Deleting security group rule with ID - %s',
460                 sec_grp_rule.id)
461     neutron.delete_security_group_rule(sec_grp_rule.id)
462
463
464 def get_rules_by_security_group(neutron, sec_grp):
465     """
466     Retrieves all of the rules for a given security group
467     :param neutron: the client
468     :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
469     """
470     logger.info('Retrieving security group rules associate with the '
471                 'security group - %s', sec_grp.name)
472     out = list()
473     rules = neutron.list_security_group_rules(
474         **{'security_group_id': sec_grp.id})
475     for rule in rules['security_group_rules']:
476         if rule['security_group_id'] == sec_grp.id:
477             out.append(SecurityGroupRule(**rule))
478     return out
479
480
481 def get_rule_by_id(neutron, sec_grp, rule_id):
482     """
483     Deletes a security group object from OpenStack
484     :param neutron: the client
485     :param sec_grp: the SNAPS SecurityGroup domain object
486     :param rule_id: the rule's ID
487     :param sec_grp: a SNAPS SecurityGroupRule domain object
488     """
489     rules = neutron.list_security_group_rules(
490         **{'security_group_id': sec_grp.id})
491     for rule in rules['security_group_rules']:
492         if rule['id'] == rule_id:
493             return SecurityGroupRule(**rule)
494     return None
495
496
497 def get_external_networks(neutron):
498     """
499     Returns a list of external OpenStack network object/dict for all external
500     networks
501     :param neutron: the client
502     :return: a list of external networks of Type SNAPS-OO domain class Network
503     """
504     out = list()
505     for network in neutron.list_networks(
506             **{'router:external': True})['networks']:
507         out.append(Network(**network))
508     return out
509
510
511 def get_floating_ips(neutron, ports=None):
512     """
513     Returns all of the floating IPs
514     When ports is not None, FIPs returned must be associated with one of the
515     ports in the list and a tuple 2 where the first element being the port's
516     name and the second being the FloatingIp SNAPS-OO domain object.
517     When ports is None, all known FloatingIp SNAPS-OO domain objects will be
518     returned in a list
519     :param neutron: the Neutron client
520     :param ports: a list of SNAPS-OO Port objects to join
521     :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
522              is not None else a list of Port objects
523     """
524     out = list()
525     fips = neutron.list_floatingips()
526     for fip in fips['floatingips']:
527         if ports:
528             for port_name, port in ports:
529                 if fip['port_id'] == port.id:
530                     out.append((port.name, FloatingIp(
531                         inst_id=fip['id'], ip=fip['floating_ip_address'])))
532                     break
533         else:
534             out.append(FloatingIp(inst_id=fip['id'],
535                                   ip=fip['floating_ip_address']))
536
537     return out
538
539
540 def create_floating_ip(neutron, ext_net_name):
541     """
542     Returns the floating IP object that was created with this call
543     :param neutron: the Neutron client
544     :param ext_net_name: the name of the external network on which to apply the
545                          floating IP address
546     :return: the SNAPS FloatingIp object
547     """
548     logger.info('Creating floating ip to external network - ' + ext_net_name)
549     ext_net = get_network(neutron, network_name=ext_net_name)
550     if ext_net:
551         fip = neutron.create_floatingip(
552             body={'floatingip':
553                   {'floating_network_id': ext_net.id}})
554
555         return FloatingIp(inst_id=fip['floatingip']['id'],
556                           ip=fip['floatingip']['floating_ip_address'])
557     else:
558         raise NeutronException(
559             'Cannot create floating IP, external network not found')
560
561
562 def get_floating_ip(neutron, floating_ip):
563     """
564     Returns a floating IP object that should be identical to the floating_ip
565     parameter
566     :param neutron: the Neutron client
567     :param floating_ip: the SNAPS FloatingIp object
568     :return: hopefully the same floating IP object input
569     """
570     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
571                  floating_ip.ip)
572     os_fip = __get_os_floating_ip(neutron, floating_ip)
573     if os_fip:
574         return FloatingIp(
575             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
576
577
578 def __get_os_floating_ip(neutron, floating_ip):
579     """
580     Returns an OpenStack floating IP object
581     parameter
582     :param neutron: the Neutron client
583     :param floating_ip: the SNAPS FloatingIp object
584     :return: hopefully the same floating IP object input
585     """
586     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
587                  floating_ip.ip)
588     fips = neutron.list_floatingips(ip=floating_ip.id)
589
590     for fip in fips['floatingips']:
591         if fip['id'] == floating_ip.id:
592             return fip
593
594
595 def delete_floating_ip(neutron, floating_ip):
596     """
597     Responsible for deleting a floating IP
598     :param neutron: the Neutron client
599     :param floating_ip: the SNAPS FloatingIp object
600     :return:
601     """
602     logger.debug('Attempting to delete existing floating ip with IP - %s',
603                  floating_ip.ip)
604     return neutron.delete_floatingip(floating_ip.id)
605
606
607 class NeutronException(Exception):
608     """
609     Exception when calls to the Keystone client cannot be served properly
610     """