SNAPS Stack creators can now return SNAPS network creators.
[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 get_subnet_by_id(neutron, subnet_id):
191     """
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
196     """
197     os_subnet = neutron.show_subnet(subnet_id)
198     if os_subnet and 'subnet' in os_subnet:
199         return Subnet(**os_subnet['subnet'])
200
201
202 def get_subnets_by_network(neutron, network):
203     """
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
208     """
209     out = list()
210
211     os_subnets = neutron.list_subnets(network_id=network.id)
212
213     for os_subnet in os_subnets['subnets']:
214         out.append(Subnet(**os_subnet))
215
216     return out
217
218
219 def create_router(neutron, os_creds, router_settings):
220     """
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
226                             JSON body
227     :return: a SNAPS-OO Router domain object
228     """
229     if neutron:
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'])
234     else:
235         logger.error("Failed to create router.")
236         raise NeutronException('Failed to create router')
237
238
239 def delete_router(neutron, router):
240     """
241     Deletes a router for OpenStack
242     :param neutron: the client
243     :param router: a SNAPS-OO Router domain object
244     """
245     if neutron and router:
246         logger.info('Deleting router with name - ' + router.name)
247         neutron.delete_router(router=router.id)
248
249
250 def get_router(neutron, router_settings=None, router_name=None):
251     """
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
254     parameter, else None
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
259     """
260     router_filter = dict()
261     if router_settings:
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
265     elif router_name:
266         router_filter['name'] = router_name
267     else:
268         return None
269
270     routers = neutron.list_routers(**router_filter)
271     for routerInst in routers['routers']:
272         return Router(**routerInst)
273     return None
274
275
276 def add_interface_router(neutron, router, subnet=None, port=None):
277     """
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
285     """
286     if subnet and port:
287         raise NeutronException(
288             'Cannot add interface to the router. Both subnet and '
289             'port were sent in. Either or please.')
290
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)
296     else:
297         raise NeutronException(
298             'Unable to create interface router as neutron client,'
299             ' router or subnet were not created')
300
301
302 def remove_interface_router(neutron, router, subnet=None, port=None):
303     """
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
309     """
310     if router:
311         try:
312             logger.info('Removing router interface from router named ' +
313                         router.name)
314             neutron.remove_interface_router(
315                 router=router.id,
316                 body=__create_port_json_body(subnet, port))
317         except NotFound as e:
318             logger.warning('Could not remove router interface. NotFound - %s',
319                            e)
320             pass
321     else:
322         logger.warning('Could not remove router interface, No router object')
323
324
325 def __create_port_json_body(subnet=None, port=None):
326     """
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
332     :return: the dict
333     """
334     if subnet and port:
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')
340
341     if subnet:
342         return {"subnet_id": subnet.id}
343     else:
344         return {"port_id": port.id}
345
346
347 def create_port(neutron, os_creds, port_settings):
348     """
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
354     """
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'])
363
364
365 def delete_port(neutron, port):
366     """
367     Removes an OpenStack port
368     :param neutron: the client
369     :param port: the SNAPS-OO Port domain object
370     """
371     logger.info('Deleting port with name ' + port.name)
372     neutron.delete_port(port.id)
373
374
375 def get_port(neutron, port_settings=None, port_name=None):
376     """
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
381                       into the query
382     :return: a SNAPS-OO Port domain object
383     """
384     port_filter = dict()
385
386     if port_settings:
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
394     elif port_name:
395         port_filter['name'] = port_name
396
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'])
401     return None
402
403
404 def create_security_group(neutron, keystone, sec_grp_settings):
405     """
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
411     """
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'])
417
418
419 def delete_security_group(neutron, sec_grp):
420     """
421     Deletes a security group object from OpenStack
422     :param neutron: the client
423     :param sec_grp: the SNAPS SecurityGroup object to delete
424     """
425     logger.info('Deleting security group with name - %s', sec_grp.name)
426     neutron.delete_security_group(sec_grp.id)
427
428
429 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
430                        project_id=None):
431     """
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
442     """
443
444     sec_grp_filter = dict()
445     if project_id:
446         sec_grp_filter['tenant_id'] = project_id
447
448     if sec_grp_settings:
449         sec_grp_filter['name'] = sec_grp_settings.name
450
451         if sec_grp_settings.description:
452             sec_grp_filter['description'] = sec_grp_settings.description
453     elif sec_grp_name:
454         sec_grp_filter['name'] = sec_grp_name
455     else:
456         return None
457
458     groups = neutron.list_security_groups(**sec_grp_filter)
459     for group in groups['security_groups']:
460         return SecurityGroup(**group)
461
462
463 def get_security_group_by_id(neutron, sec_grp_id):
464     """
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
469     """
470     logger.info('Retrieving security group with ID - ' + sec_grp_id)
471
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)
476     return None
477
478
479 def create_security_group_rule(neutron, sec_grp_rule_settings):
480     """
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
485     """
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'])
491
492
493 def delete_security_group_rule(neutron, sec_grp_rule):
494     """
495     Deletes a security group object from OpenStack
496     :param neutron: the client
497     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
498     """
499     logger.info('Deleting security group rule with ID - %s',
500                 sec_grp_rule.id)
501     neutron.delete_security_group_rule(sec_grp_rule.id)
502
503
504 def get_rules_by_security_group(neutron, sec_grp):
505     """
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
509     """
510     logger.info('Retrieving security group rules associate with the '
511                 'security group - %s', sec_grp.name)
512     out = list()
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))
518     return out
519
520
521 def get_rule_by_id(neutron, sec_grp, rule_id):
522     """
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
528     """
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)
534     return None
535
536
537 def get_external_networks(neutron):
538     """
539     Returns a list of external OpenStack network object/dict for all external
540     networks
541     :param neutron: the client
542     :return: a list of external networks of Type SNAPS-OO domain class Network
543     """
544     out = list()
545     for network in neutron.list_networks(
546             **{'router:external': True})['networks']:
547         out.append(Network(**network))
548     return out
549
550
551 def get_floating_ips(neutron, ports=None):
552     """
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
558     returned in a list
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
563     """
564     out = list()
565     fips = neutron.list_floatingips()
566     for fip in fips['floatingips']:
567         if ports:
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'])))
572                     break
573         else:
574             out.append(FloatingIp(inst_id=fip['id'],
575                                   ip=fip['floating_ip_address']))
576
577     return out
578
579
580 def create_floating_ip(neutron, ext_net_name):
581     """
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
585                          floating IP address
586     :return: the SNAPS FloatingIp object
587     """
588     logger.info('Creating floating ip to external network - ' + ext_net_name)
589     ext_net = get_network(neutron, network_name=ext_net_name)
590     if ext_net:
591         fip = neutron.create_floatingip(
592             body={'floatingip':
593                   {'floating_network_id': ext_net.id}})
594
595         return FloatingIp(inst_id=fip['floatingip']['id'],
596                           ip=fip['floatingip']['floating_ip_address'])
597     else:
598         raise NeutronException(
599             'Cannot create floating IP, external network not found')
600
601
602 def get_floating_ip(neutron, floating_ip):
603     """
604     Returns a floating IP object that should be identical to the floating_ip
605     parameter
606     :param neutron: the Neutron client
607     :param floating_ip: the SNAPS FloatingIp object
608     :return: hopefully the same floating IP object input
609     """
610     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
611                  floating_ip.ip)
612     os_fip = __get_os_floating_ip(neutron, floating_ip)
613     if os_fip:
614         return FloatingIp(
615             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
616
617
618 def __get_os_floating_ip(neutron, floating_ip):
619     """
620     Returns an OpenStack floating IP object
621     parameter
622     :param neutron: the Neutron client
623     :param floating_ip: the SNAPS FloatingIp object
624     :return: hopefully the same floating IP object input
625     """
626     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
627                  floating_ip.ip)
628     fips = neutron.list_floatingips(ip=floating_ip.id)
629
630     for fip in fips['floatingips']:
631         if fip['id'] == floating_ip.id:
632             return fip
633
634
635 def delete_floating_ip(neutron, floating_ip):
636     """
637     Responsible for deleting a floating IP
638     :param neutron: the Neutron client
639     :param floating_ip: the SNAPS FloatingIp object
640     :return:
641     """
642     logger.debug('Attempting to delete existing floating ip with IP - %s',
643                  floating_ip.ip)
644     return neutron.delete_floatingip(floating_ip.id)
645
646
647 class NeutronException(Exception):
648     """
649     Exception when calls to the Keystone client cannot be served properly
650     """