Merge "Scripts to allow Jenkins to run the SNAPS-OO tests."
[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.project import NetworkQuotas
24 from snaps.domain.vm_inst import FloatingIp
25 from snaps.openstack.utils import keystone_utils
26
27 __author__ = 'spisarski'
28
29 logger = logging.getLogger('neutron_utils')
30
31 """
32 Utilities for basic neutron API calls
33 """
34
35
36 def neutron_client(os_creds):
37     """
38     Instantiates and returns a client for communications with OpenStack's
39     Neutron server
40     :param os_creds: the credentials for connecting to the OpenStack remote API
41     :return: the client object
42     """
43     return Client(api_version=os_creds.network_api_version,
44                   session=keystone_utils.keystone_session(os_creds),
45                   region_name=os_creds.region_name)
46
47
48 def create_network(neutron, os_creds, network_settings):
49     """
50     Creates a network for OpenStack
51     :param neutron: the client
52     :param os_creds: the OpenStack credentials
53     :param network_settings: A dictionary containing the network configuration
54                              and is responsible for creating the network
55                             request JSON body
56     :return: a SNAPS-OO Network domain object
57     """
58     if neutron and network_settings:
59         logger.info('Creating network with name ' + network_settings.name)
60         json_body = network_settings.dict_for_neutron(os_creds)
61         os_network = neutron.create_network(body=json_body)
62         return Network(**os_network['network'])
63     else:
64         raise NeutronException('Failded to create network')
65
66
67 def delete_network(neutron, network):
68     """
69     Deletes a network for OpenStack
70     :param neutron: the client
71     :param network: a SNAPS-OO Network domain object
72     """
73     if neutron and network:
74         logger.info('Deleting network with name ' + network.name)
75         neutron.delete_network(network.id)
76
77
78 def get_network(neutron, network_settings=None, network_name=None,
79                 project_id=None):
80     """
81     Returns Network SNAPS-OO domain object the first network found with
82     either the given attributes from the network_settings object if not None,
83     else the query will use just the name from the network_name parameter.
84     When the project_id is included, that will be added to the query filter.
85     :param neutron: the client
86     :param network_settings: the NetworkSettings object used to create filter
87     :param network_name: the name of the network to retrieve
88     :param project_id: the id of the network's project
89     :return: a SNAPS-OO Network domain object
90     """
91     net_filter = dict()
92     if network_settings:
93         net_filter['name'] = network_settings.name
94     elif network_name:
95         net_filter['name'] = network_name
96
97     if project_id:
98         net_filter['project_id'] = project_id
99
100     networks = neutron.list_networks(**net_filter)
101     for network, netInsts in networks.items():
102         for inst in netInsts:
103             return Network(**inst)
104
105
106 def get_network_by_id(neutron, network_id):
107     """
108     Returns the network object (dictionary) with the given ID else None
109     :param neutron: the client
110     :param network_id: the id of the network to retrieve
111     :return: a SNAPS-OO Network domain object
112     """
113     networks = neutron.list_networks(**{'id': network_id})
114     for network in networks['networks']:
115         if network['id'] == network_id:
116             return Network(**network)
117
118
119 def create_subnet(neutron, subnet_settings, os_creds, network=None):
120     """
121     Creates a network subnet for OpenStack
122     :param neutron: the client
123     :param network: the network object
124     :param subnet_settings: A dictionary containing the subnet configuration
125                             and is responsible for creating the subnet request
126                             JSON body
127     :param os_creds: the OpenStack credentials
128     :return: a SNAPS-OO Subnet domain object
129     """
130     if neutron and network and subnet_settings:
131         json_body = {'subnets': [subnet_settings.dict_for_neutron(
132             os_creds, network=network)]}
133         logger.info('Creating subnet with name ' + subnet_settings.name)
134         subnets = neutron.create_subnet(body=json_body)
135         return Subnet(**subnets['subnets'][0])
136     else:
137         raise NeutronException('Failed to create subnet')
138
139
140 def delete_subnet(neutron, subnet):
141     """
142     Deletes a network subnet for OpenStack
143     :param neutron: the client
144     :param subnet: a SNAPS-OO Subnet domain object
145     """
146     if neutron and subnet:
147         logger.info('Deleting subnet with name ' + subnet.name)
148         neutron.delete_subnet(subnet.id)
149
150
151 def get_subnet(neutron, subnet_settings=None, subnet_name=None):
152     """
153     Returns the first subnet object that fits the query else None including
154     if subnet_settings or subnet_name parameters are None.
155     :param neutron: the client
156     :param subnet_settings: the subnet settings of the object to retrieve
157     :param subnet_name: the name of the subnet to retrieve
158     :return: a SNAPS-OO Subnet domain object or None
159     """
160     sub_filter = dict()
161     if subnet_settings:
162         sub_filter['name'] = subnet_settings.name
163         sub_filter['cidr'] = subnet_settings.cidr
164         if subnet_settings.gateway_ip:
165             sub_filter['gateway_ip'] = subnet_settings.gateway_ip
166
167         if subnet_settings.enable_dhcp is not None:
168             sub_filter['enable_dhcp'] = subnet_settings.enable_dhcp
169
170         if subnet_settings.destination:
171             sub_filter['destination'] = subnet_settings.destination
172
173         if subnet_settings.nexthop:
174             sub_filter['nexthop'] = subnet_settings.nexthop
175
176         if subnet_settings.ipv6_ra_mode:
177             sub_filter['ipv6_ra_mode'] = subnet_settings.ipv6_ra_mode
178
179         if subnet_settings.ipv6_address_mode:
180             sub_filter['ipv6_address_mode'] = subnet_settings.ipv6_address_mode
181     elif subnet_name:
182         sub_filter['name'] = subnet_name
183     else:
184         return None
185
186     subnets = neutron.list_subnets(**sub_filter)
187     for subnet in subnets['subnets']:
188         return Subnet(**subnet)
189
190
191 def get_subnet_by_id(neutron, subnet_id):
192     """
193     Returns a SNAPS-OO Subnet domain object for a given ID
194     :param neutron: the OpenStack neutron client
195     :param subnet_id: the subnet ID
196     :return: a Subnet object
197     """
198     os_subnet = neutron.show_subnet(subnet_id)
199     if os_subnet and 'subnet' in os_subnet:
200         return Subnet(**os_subnet['subnet'])
201
202
203 def get_subnets_by_network(neutron, network):
204     """
205     Returns a list of SNAPS-OO Subnet domain objects
206     :param neutron: the OpenStack neutron client
207     :param network: the SNAPS-OO Network domain object
208     :return: a list of Subnet objects
209     """
210     out = list()
211
212     os_subnets = neutron.list_subnets(network_id=network.id)
213
214     for os_subnet in os_subnets['subnets']:
215         out.append(Subnet(**os_subnet))
216
217     return out
218
219
220 def create_router(neutron, os_creds, router_settings):
221     """
222     Creates a router for OpenStack
223     :param neutron: the client
224     :param os_creds: the OpenStack credentials
225     :param router_settings: A dictionary containing the router configuration
226                             and is responsible for creating the subnet request
227                             JSON body
228     :return: a SNAPS-OO Router domain object
229     """
230     if neutron:
231         json_body = router_settings.dict_for_neutron(neutron, os_creds)
232         logger.info('Creating router with name - ' + router_settings.name)
233         os_router = neutron.create_router(json_body)
234         return Router(**os_router['router'])
235     else:
236         logger.error("Failed to create router.")
237         raise NeutronException('Failed to create router')
238
239
240 def delete_router(neutron, router):
241     """
242     Deletes a router for OpenStack
243     :param neutron: the client
244     :param router: a SNAPS-OO Router domain object
245     """
246     if neutron and router:
247         logger.info('Deleting router with name - ' + router.name)
248         neutron.delete_router(router=router.id)
249
250
251 def get_router(neutron, router_settings=None, router_name=None):
252     """
253     Returns the first router object (dictionary) found the given the settings
254     values if not None, else finds the first with the value of the router_name
255     parameter, else None
256     :param neutron: the client
257     :param router_settings: the RouterSettings object
258     :param router_name: the name of the network to retrieve
259     :return: a SNAPS-OO Router domain object
260     """
261     router_filter = dict()
262     if router_settings:
263         router_filter['name'] = router_settings.name
264         if router_settings.admin_state_up is not None:
265             router_filter['admin_state_up'] = router_settings.admin_state_up
266     elif router_name:
267         router_filter['name'] = router_name
268     else:
269         return None
270
271     routers = neutron.list_routers(**router_filter)
272     for routerInst in routers['routers']:
273         return Router(**routerInst)
274     return None
275
276
277 def add_interface_router(neutron, router, subnet=None, port=None):
278     """
279     Adds an interface router for OpenStack for either a subnet or port.
280     Exception will be raised if requesting for both.
281     :param neutron: the client
282     :param router: the router object
283     :param subnet: the subnet object
284     :param port: the port object
285     :return: the interface router object
286     """
287     if subnet and port:
288         raise NeutronException(
289             'Cannot add interface to the router. Both subnet and '
290             'port were sent in. Either or please.')
291
292     if neutron and router and (router or subnet):
293         logger.info('Adding interface to router with name ' + router.name)
294         os_intf_router = neutron.add_interface_router(
295             router=router.id, body=__create_port_json_body(subnet, port))
296         return InterfaceRouter(**os_intf_router)
297     else:
298         raise NeutronException(
299             'Unable to create interface router as neutron client,'
300             ' router or subnet were not created')
301
302
303 def remove_interface_router(neutron, router, subnet=None, port=None):
304     """
305     Removes an interface router for OpenStack
306     :param neutron: the client
307     :param router: the SNAPS-OO Router domain object
308     :param subnet: the subnet object (either subnet or port, not both)
309     :param port: the port object
310     """
311     if router:
312         try:
313             logger.info('Removing router interface from router named ' +
314                         router.name)
315             neutron.remove_interface_router(
316                 router=router.id,
317                 body=__create_port_json_body(subnet, port))
318         except NotFound as e:
319             logger.warning('Could not remove router interface. NotFound - %s',
320                            e)
321             pass
322     else:
323         logger.warning('Could not remove router interface, No router object')
324
325
326 def __create_port_json_body(subnet=None, port=None):
327     """
328     Returns the dictionary required for creating and deleting router
329     interfaces. Will only work on a subnet or port object. Will throw and
330     exception if parameters contain both or neither
331     :param subnet: the subnet object
332     :param port: the port object
333     :return: the dict
334     """
335     if subnet and port:
336         raise NeutronException(
337             'Cannot create JSON body with both subnet and port')
338     if not subnet and not port:
339         raise NeutronException(
340             'Cannot create JSON body without subnet or port')
341
342     if subnet:
343         return {"subnet_id": subnet.id}
344     else:
345         return {"port_id": port.id}
346
347
348 def create_port(neutron, os_creds, port_settings):
349     """
350     Creates a port for OpenStack
351     :param neutron: the client
352     :param os_creds: the OpenStack credentials
353     :param port_settings: the settings object for port configuration
354     :return: the SNAPS-OO Port domain object
355     """
356     json_body = port_settings.dict_for_neutron(neutron, os_creds)
357     logger.info('Creating port for network with name - %s',
358                 port_settings.network_name)
359     os_port = neutron.create_port(body=json_body)['port']
360     return Port(name=os_port['name'], id=os_port['id'],
361                 ips=os_port['fixed_ips'],
362                 mac_address=os_port['mac_address'],
363                 allowed_address_pairs=os_port['allowed_address_pairs'])
364
365
366 def delete_port(neutron, port):
367     """
368     Removes an OpenStack port
369     :param neutron: the client
370     :param port: the SNAPS-OO Port domain object
371     """
372     logger.info('Deleting port with name ' + port.name)
373     neutron.delete_port(port.id)
374
375
376 def get_port(neutron, port_settings=None, port_name=None):
377     """
378     Returns the first port object (dictionary) found for the given query
379     :param neutron: the client
380     :param port_settings: the PortSettings object used for generating the query
381     :param port_name: if port_settings is None, this name is the value to place
382                       into the query
383     :return: a SNAPS-OO Port domain object
384     """
385     port_filter = dict()
386
387     if port_settings:
388         port_filter['name'] = port_settings.name
389         if port_settings.admin_state_up:
390             port_filter['admin_state_up'] = port_settings.admin_state_up
391         if port_settings.device_id:
392             port_filter['device_id'] = port_settings.device_id
393         if port_settings.mac_address:
394             port_filter['mac_address'] = port_settings.mac_address
395     elif port_name:
396         port_filter['name'] = port_name
397
398     ports = neutron.list_ports(**port_filter)
399     for port in ports['ports']:
400         return Port(name=port['name'], id=port['id'],
401                     ips=port['fixed_ips'], mac_address=port['mac_address'])
402     return None
403
404
405 def create_security_group(neutron, keystone, sec_grp_settings):
406     """
407     Creates a security group object in OpenStack
408     :param neutron: the Neutron client
409     :param keystone: the Keystone client
410     :param sec_grp_settings: the security group settings
411     :return: a SNAPS-OO SecurityGroup domain object
412     """
413     logger.info('Creating security group with name - %s',
414                 sec_grp_settings.name)
415     os_group = neutron.create_security_group(
416         sec_grp_settings.dict_for_neutron(keystone))
417     return SecurityGroup(**os_group['security_group'])
418
419
420 def delete_security_group(neutron, sec_grp):
421     """
422     Deletes a security group object from OpenStack
423     :param neutron: the client
424     :param sec_grp: the SNAPS SecurityGroup object to delete
425     """
426     logger.info('Deleting security group with name - %s', sec_grp.name)
427     neutron.delete_security_group(sec_grp.id)
428
429
430 def get_security_group(neutron, sec_grp_settings=None, sec_grp_name=None,
431                        project_id=None):
432     """
433     Returns the first security group for a given query. The query gets built
434     from the sec_grp_settings parameter if not None, else only the name of
435     the security group will be used, else if the query parameters are None then
436     None will be returned
437     :param neutron: the client
438     :param sec_grp_settings: an instance of SecurityGroupSettings config object
439     :param sec_grp_name: the name of security group object to retrieve
440     :param project_id: the ID of the project/tentant object that owns the
441                        secuity group to retrieve
442     :return: a SNAPS-OO SecurityGroup domain object or None if not found
443     """
444
445     sec_grp_filter = dict()
446     if project_id:
447         sec_grp_filter['tenant_id'] = project_id
448
449     if sec_grp_settings:
450         sec_grp_filter['name'] = sec_grp_settings.name
451
452         if sec_grp_settings.description:
453             sec_grp_filter['description'] = sec_grp_settings.description
454     elif sec_grp_name:
455         sec_grp_filter['name'] = sec_grp_name
456     else:
457         return None
458
459     groups = neutron.list_security_groups(**sec_grp_filter)
460     for group in groups['security_groups']:
461         return SecurityGroup(**group)
462
463
464 def get_security_group_by_id(neutron, sec_grp_id):
465     """
466     Returns the first security group object of the given name else None
467     :param neutron: the client
468     :param sec_grp_id: the id of the security group to retrieve
469     :return: a SNAPS-OO SecurityGroup domain object or None if not found
470     """
471     logger.info('Retrieving security group with ID - ' + sec_grp_id)
472
473     groups = neutron.list_security_groups(**{'id': sec_grp_id})
474     for group in groups['security_groups']:
475         if group['id'] == sec_grp_id:
476             return SecurityGroup(**group)
477     return None
478
479
480 def create_security_group_rule(neutron, sec_grp_rule_settings):
481     """
482     Creates a security group object in OpenStack
483     :param neutron: the client
484     :param sec_grp_rule_settings: the security group rule settings
485     :return: a SNAPS-OO SecurityGroupRule domain object
486     """
487     logger.info('Creating security group to security group - %s',
488                 sec_grp_rule_settings.sec_grp_name)
489     os_rule = neutron.create_security_group_rule(
490         sec_grp_rule_settings.dict_for_neutron(neutron))
491     return SecurityGroupRule(**os_rule['security_group_rule'])
492
493
494 def delete_security_group_rule(neutron, sec_grp_rule):
495     """
496     Deletes a security group object from OpenStack
497     :param neutron: the client
498     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
499     """
500     logger.info('Deleting security group rule with ID - %s',
501                 sec_grp_rule.id)
502     neutron.delete_security_group_rule(sec_grp_rule.id)
503
504
505 def get_rules_by_security_group(neutron, sec_grp):
506     """
507     Retrieves all of the rules for a given security group
508     :param neutron: the client
509     :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
510     """
511     logger.info('Retrieving security group rules associate with the '
512                 'security group - %s', sec_grp.name)
513     out = list()
514     rules = neutron.list_security_group_rules(
515         **{'security_group_id': sec_grp.id})
516     for rule in rules['security_group_rules']:
517         if rule['security_group_id'] == sec_grp.id:
518             out.append(SecurityGroupRule(**rule))
519     return out
520
521
522 def get_rule_by_id(neutron, sec_grp, rule_id):
523     """
524     Deletes a security group object from OpenStack
525     :param neutron: the client
526     :param sec_grp: the SNAPS SecurityGroup domain object
527     :param rule_id: the rule's ID
528     :param sec_grp: a SNAPS SecurityGroupRule domain object
529     """
530     rules = neutron.list_security_group_rules(
531         **{'security_group_id': sec_grp.id})
532     for rule in rules['security_group_rules']:
533         if rule['id'] == rule_id:
534             return SecurityGroupRule(**rule)
535     return None
536
537
538 def get_external_networks(neutron):
539     """
540     Returns a list of external OpenStack network object/dict for all external
541     networks
542     :param neutron: the client
543     :return: a list of external networks of Type SNAPS-OO domain class Network
544     """
545     out = list()
546     for network in neutron.list_networks(
547             **{'router:external': True})['networks']:
548         out.append(Network(**network))
549     return out
550
551
552 def get_floating_ips(neutron, ports=None):
553     """
554     Returns all of the floating IPs
555     When ports is not None, FIPs returned must be associated with one of the
556     ports in the list and a tuple 2 where the first element being the port's
557     name and the second being the FloatingIp SNAPS-OO domain object.
558     When ports is None, all known FloatingIp SNAPS-OO domain objects will be
559     returned in a list
560     :param neutron: the Neutron client
561     :param ports: a list of SNAPS-OO Port objects to join
562     :return: a list of tuple 2 (port_name, SNAPS FloatingIp) objects when ports
563              is not None else a list of Port objects
564     """
565     out = list()
566     fips = neutron.list_floatingips()
567     for fip in fips['floatingips']:
568         if ports:
569             for port_name, port in ports:
570                 if fip['port_id'] == port.id:
571                     out.append((port.name, FloatingIp(
572                         inst_id=fip['id'], ip=fip['floating_ip_address'])))
573                     break
574         else:
575             out.append(FloatingIp(inst_id=fip['id'],
576                                   ip=fip['floating_ip_address']))
577
578     return out
579
580
581 def create_floating_ip(neutron, ext_net_name):
582     """
583     Returns the floating IP object that was created with this call
584     :param neutron: the Neutron client
585     :param ext_net_name: the name of the external network on which to apply the
586                          floating IP address
587     :return: the SNAPS FloatingIp object
588     """
589     logger.info('Creating floating ip to external network - ' + ext_net_name)
590     ext_net = get_network(neutron, network_name=ext_net_name)
591     if ext_net:
592         fip = neutron.create_floatingip(
593             body={'floatingip':
594                   {'floating_network_id': ext_net.id}})
595
596         return FloatingIp(inst_id=fip['floatingip']['id'],
597                           ip=fip['floatingip']['floating_ip_address'])
598     else:
599         raise NeutronException(
600             'Cannot create floating IP, external network not found')
601
602
603 def get_floating_ip(neutron, floating_ip):
604     """
605     Returns a floating IP object that should be identical to the floating_ip
606     parameter
607     :param neutron: the Neutron client
608     :param floating_ip: the SNAPS FloatingIp object
609     :return: hopefully the same floating IP object input
610     """
611     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
612                  floating_ip.ip)
613     os_fip = __get_os_floating_ip(neutron, floating_ip)
614     if os_fip:
615         return FloatingIp(
616             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
617
618
619 def __get_os_floating_ip(neutron, floating_ip):
620     """
621     Returns an OpenStack floating IP object
622     parameter
623     :param neutron: the Neutron client
624     :param floating_ip: the SNAPS FloatingIp object
625     :return: hopefully the same floating IP object input
626     """
627     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
628                  floating_ip.ip)
629     fips = neutron.list_floatingips(ip=floating_ip.id)
630
631     for fip in fips['floatingips']:
632         if fip['id'] == floating_ip.id:
633             return fip
634
635
636 def delete_floating_ip(neutron, floating_ip):
637     """
638     Responsible for deleting a floating IP
639     :param neutron: the Neutron client
640     :param floating_ip: the SNAPS FloatingIp object
641     :return:
642     """
643     logger.debug('Attempting to delete existing floating ip with IP - %s',
644                  floating_ip.ip)
645     return neutron.delete_floatingip(floating_ip.id)
646
647
648 def get_network_quotas(neutron, project_id):
649     """
650     Returns a list of all available keypairs
651     :param nova: the Nova client
652     :param project_id: the project's ID of the quotas to lookup
653     :return: an object of type NetworkQuotas or None if not found
654     """
655     quota = neutron.show_quota(project_id)
656     if quota:
657         return NetworkQuotas(**quota['quota'])
658
659
660 def update_quotas(neutron, project_id, network_quotas):
661     """
662     Updates the networking quotas for a given project
663     :param neutron: the Neutron client
664     :param project_id: the project's ID that requires quota updates
665     :param network_quotas: an object of type NetworkQuotas containing the
666                            values to update
667     :return:
668     """
669     update_body = dict()
670     update_body['security_group'] = network_quotas.security_group
671     update_body['security_group_rule'] = network_quotas.security_group_rule
672     update_body['floatingip'] = network_quotas.floatingip
673     update_body['network'] = network_quotas.network
674     update_body['port'] = network_quotas.port
675     update_body['router'] = network_quotas.router
676     update_body['subnet'] = network_quotas.subnet
677
678     return neutron.update_quota(project_id, {'quota': update_body})
679
680
681 class NeutronException(Exception):
682     """
683     Exception when calls to the Keystone client cannot be served properly
684     """