Merge "Added proper assertion config NIC test."
[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
45
46 def create_network(neutron, os_creds, network_settings):
47     """
48     Creates a network for OpenStack
49     :param neutron: the client
50     :param os_creds: the OpenStack credentials
51     :param network_settings: A dictionary containing the network configuration
52                              and is responsible for creating the network
53                             request JSON body
54     :return: a SNAPS-OO Network domain object
55     """
56     if neutron and network_settings:
57         logger.info('Creating network with name ' + network_settings.name)
58         json_body = network_settings.dict_for_neutron(os_creds)
59         os_network = neutron.create_network(body=json_body)
60         return Network(**os_network['network'])
61     else:
62         raise NeutronException('Failded to create network')
63
64
65 def delete_network(neutron, network):
66     """
67     Deletes a network for OpenStack
68     :param neutron: the client
69     :param network: a SNAPS-OO Network domain object
70     """
71     if neutron and network:
72         logger.info('Deleting network with name ' + network.name)
73         neutron.delete_network(network.id)
74
75
76 def get_network(neutron, network_name, project_id=None):
77     """
78     Returns an object (dictionary) of the first network found with a given name
79     and project_id (if included)
80     :param neutron: the client
81     :param network_name: the name of the network to retrieve
82     :param project_id: the id of the network's project
83     :return: a SNAPS-OO Network domain object
84     """
85     net_filter = dict()
86     if network_name:
87         net_filter['name'] = network_name
88     if project_id:
89         net_filter['project_id'] = project_id
90
91     networks = neutron.list_networks(**net_filter)
92     for network, netInsts in networks.items():
93         for inst in netInsts:
94             if inst.get('name') == network_name:
95                 return Network(**inst)
96     return None
97
98
99 def get_network_by_id(neutron, network_id):
100     """
101     Returns the network object (dictionary) with the given ID
102     :param neutron: the client
103     :param network_id: the id of the network to retrieve
104     :return: a SNAPS-OO Network domain object
105     """
106     networks = neutron.list_networks(**{'id': network_id})
107     for network, netInsts in networks.items():
108         for inst in netInsts:
109             if inst.get('id') == network_id:
110                 return Network(**inst)
111     return None
112
113
114 def create_subnet(neutron, subnet_settings, os_creds, network=None):
115     """
116     Creates a network subnet for OpenStack
117     :param neutron: the client
118     :param network: the network object
119     :param subnet_settings: A dictionary containing the subnet configuration
120                             and is responsible for creating the subnet request
121                             JSON body
122     :param os_creds: the OpenStack credentials
123     :return: a SNAPS-OO Subnet domain object
124     """
125     if neutron and network and subnet_settings:
126         json_body = {'subnets': [subnet_settings.dict_for_neutron(
127             os_creds, network=network)]}
128         logger.info('Creating subnet with name ' + subnet_settings.name)
129         subnets = neutron.create_subnet(body=json_body)
130         return Subnet(**subnets['subnets'][0])
131     else:
132         raise NeutronException('Failed to create subnet')
133
134
135 def delete_subnet(neutron, subnet):
136     """
137     Deletes a network subnet for OpenStack
138     :param neutron: the client
139     :param subnet: a SNAPS-OO Subnet domain object
140     """
141     if neutron and subnet:
142         logger.info('Deleting subnet with name ' + subnet.name)
143         neutron.delete_subnet(subnet.id)
144
145
146 def get_subnet_by_name(neutron, subnet_name):
147     """
148     Returns the first subnet object (dictionary) found with a given name
149     :param neutron: the client
150     :param subnet_name: the name of the network to retrieve
151     :return: a SNAPS-OO Subnet domain object
152     """
153     subnets = neutron.list_subnets(**{'name': subnet_name})
154     for subnet, subnetInst in subnets.items():
155         for inst in subnetInst:
156             if inst['name'] == subnet_name:
157                 return Subnet(**inst)
158     return None
159
160
161 def create_router(neutron, os_creds, router_settings):
162     """
163     Creates a router for OpenStack
164     :param neutron: the client
165     :param os_creds: the OpenStack credentials
166     :param router_settings: A dictionary containing the router configuration
167                             and is responsible for creating the subnet request
168                             JSON body
169     :return: a SNAPS-OO Router domain object
170     """
171     if neutron:
172         json_body = router_settings.dict_for_neutron(neutron, os_creds)
173         logger.info('Creating router with name - ' + router_settings.name)
174         os_router = neutron.create_router(json_body)
175         return Router(**os_router['router'])
176     else:
177         logger.error("Failed to create router.")
178         raise NeutronException('Failed to create router')
179
180
181 def delete_router(neutron, router):
182     """
183     Deletes a router for OpenStack
184     :param neutron: the client
185     :param router: a SNAPS-OO Router domain object
186     """
187     if neutron and router:
188         logger.info('Deleting router with name - ' + router.name)
189         neutron.delete_router(router=router.id)
190
191
192 def get_router_by_name(neutron, router_name):
193     """
194     Returns the first router object (dictionary) found with a given name
195     :param neutron: the client
196     :param router_name: the name of the network to retrieve
197     :return: a SNAPS-OO Router domain object
198     """
199     routers = neutron.list_routers(**{'name': router_name})
200     for router, routerInst in routers.items():
201         for inst in routerInst:
202             if inst.get('name') == router_name:
203                 return Router(**inst)
204     return None
205
206
207 def add_interface_router(neutron, router, subnet=None, port=None):
208     """
209     Adds an interface router for OpenStack for either a subnet or port.
210     Exception will be raised if requesting for both.
211     :param neutron: the client
212     :param router: the router object
213     :param subnet: the subnet object
214     :param port: the port object
215     :return: the interface router object
216     """
217     if subnet and port:
218         raise NeutronException(
219             'Cannot add interface to the router. Both subnet and '
220             'port were sent in. Either or please.')
221
222     if neutron and router and (router or subnet):
223         logger.info('Adding interface to router with name ' + router.name)
224         os_intf_router = neutron.add_interface_router(
225             router=router.id, body=__create_port_json_body(subnet, port))
226         return InterfaceRouter(**os_intf_router)
227     else:
228         raise NeutronException(
229             'Unable to create interface router as neutron client,'
230             ' router or subnet were not created')
231
232
233 def remove_interface_router(neutron, router, subnet=None, port=None):
234     """
235     Removes an interface router for OpenStack
236     :param neutron: the client
237     :param router: the SNAPS-OO Router domain object
238     :param subnet: the subnet object (either subnet or port, not both)
239     :param port: the port object
240     """
241     if router:
242         try:
243             logger.info('Removing router interface from router named ' +
244                         router.name)
245             neutron.remove_interface_router(
246                 router=router.id,
247                 body=__create_port_json_body(subnet, port))
248         except NotFound as e:
249             logger.warning('Could not remove router interface. NotFound - %s',
250                            e)
251             pass
252     else:
253         logger.warning('Could not remove router interface, No router object')
254
255
256 def __create_port_json_body(subnet=None, port=None):
257     """
258     Returns the dictionary required for creating and deleting router
259     interfaces. Will only work on a subnet or port object. Will throw and
260     exception if parameters contain both or neither
261     :param subnet: the subnet object
262     :param port: the port object
263     :return: the dict
264     """
265     if subnet and port:
266         raise NeutronException(
267             'Cannot create JSON body with both subnet and port')
268     if not subnet and not port:
269         raise NeutronException(
270             'Cannot create JSON body without subnet or port')
271
272     if subnet:
273         return {"subnet_id": subnet.id}
274     else:
275         return {"port_id": port.id}
276
277
278 def create_port(neutron, os_creds, port_settings):
279     """
280     Creates a port for OpenStack
281     :param neutron: the client
282     :param os_creds: the OpenStack credentials
283     :param port_settings: the settings object for port configuration
284     :return: the SNAPS-OO Port domain object
285     """
286     json_body = port_settings.dict_for_neutron(neutron, os_creds)
287     logger.info('Creating port for network with name - %s',
288                 port_settings.network_name)
289     os_port = neutron.create_port(body=json_body)['port']
290     return Port(name=os_port['name'], id=os_port['id'],
291                 ips=os_port['fixed_ips'],
292                 mac_address=os_port['mac_address'],
293                 allowed_address_pairs=os_port['allowed_address_pairs'])
294
295
296 def delete_port(neutron, port):
297     """
298     Removes an OpenStack port
299     :param neutron: the client
300     :param port: the SNAPS-OO Port domain object
301     """
302     logger.info('Deleting port with name ' + port.name)
303     neutron.delete_port(port.id)
304
305
306 def get_port_by_name(neutron, port_name):
307     """
308     Returns the first port object (dictionary) found with a given name
309     :param neutron: the client
310     :param port_name: the name of the port to retrieve
311     :return: a SNAPS-OO Port domain object
312     """
313     ports = neutron.list_ports(**{'name': port_name})
314     for port in ports['ports']:
315         if port['name'] == port_name:
316             return Port(name=port['name'], id=port['id'],
317                         ips=port['fixed_ips'], mac_address=port['mac_address'])
318     return None
319
320
321 def create_security_group(neutron, keystone, sec_grp_settings):
322     """
323     Creates a security group object in OpenStack
324     :param neutron: the Neutron client
325     :param keystone: the Keystone client
326     :param sec_grp_settings: the security group settings
327     :return: a SNAPS-OO SecurityGroup domain object
328     """
329     logger.info('Creating security group with name - %s',
330                 sec_grp_settings.name)
331     os_group = neutron.create_security_group(
332         sec_grp_settings.dict_for_neutron(keystone))
333     return SecurityGroup(
334         id=os_group['security_group']['id'],
335         name=os_group['security_group']['name'],
336         project_id=os_group['security_group'].get(
337             'project_id', os_group['security_group'].get('tenant_id')))
338
339
340 def delete_security_group(neutron, sec_grp):
341     """
342     Deletes a security group object from OpenStack
343     :param neutron: the client
344     :param sec_grp: the SNAPS SecurityGroup object to delete
345     """
346     logger.info('Deleting security group with name - %s', sec_grp.name)
347     neutron.delete_security_group(sec_grp.id)
348
349
350 def get_security_group(neutron, name):
351     """
352     Returns the first security group object of the given name else None
353     :param neutron: the client
354     :param name: the name of security group object to retrieve
355     :return: a SNAPS-OO SecurityGroup domain object or None if not found
356     """
357     logger.info('Retrieving security group with name - ' + name)
358
359     groups = neutron.list_security_groups(**{'name': name})
360     for group in groups['security_groups']:
361         if group['name'] == name:
362             return SecurityGroup(
363                 id=group['id'], name=group['name'],
364                 project_id=group.get('project_id', group.get('tenant_id')))
365     return None
366
367
368 def get_security_group_by_id(neutron, sec_grp_id):
369     """
370     Returns the first security group object of the given name else None
371     :param neutron: the client
372     :param sec_grp_id: the id of the security group to retrieve
373     :return: a SNAPS-OO SecurityGroup domain object or None if not found
374     """
375     logger.info('Retrieving security group with ID - ' + sec_grp_id)
376
377     groups = neutron.list_security_groups(**{'id': sec_grp_id})
378     for group in groups['security_groups']:
379         if group['id'] == sec_grp_id:
380             return SecurityGroup(
381                 id=group['id'], name=group['name'],
382                 project_id=group.get('project_id', group.get('tenant_id')))
383     return None
384
385
386 def create_security_group_rule(neutron, sec_grp_rule_settings):
387     """
388     Creates a security group object in OpenStack
389     :param neutron: the client
390     :param sec_grp_rule_settings: the security group rule settings
391     :return: a SNAPS-OO SecurityGroupRule domain object
392     """
393     logger.info('Creating security group to security group - %s',
394                 sec_grp_rule_settings.sec_grp_name)
395     os_rule = neutron.create_security_group_rule(
396         sec_grp_rule_settings.dict_for_neutron(neutron))
397     return SecurityGroupRule(**os_rule['security_group_rule'])
398
399
400 def delete_security_group_rule(neutron, sec_grp_rule):
401     """
402     Deletes a security group object from OpenStack
403     :param neutron: the client
404     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
405     """
406     logger.info('Deleting security group rule with ID - %s',
407                 sec_grp_rule.id)
408     neutron.delete_security_group_rule(sec_grp_rule.id)
409
410
411 def get_rules_by_security_group(neutron, sec_grp):
412     """
413     Retrieves all of the rules for a given security group
414     :param neutron: the client
415     :param sec_grp: a list of SNAPS SecurityGroupRule domain objects
416     """
417     logger.info('Retrieving security group rules associate with the '
418                 'security group - %s', sec_grp.name)
419     out = list()
420     rules = neutron.list_security_group_rules(
421         **{'security_group_id': sec_grp.id})
422     for rule in rules['security_group_rules']:
423         if rule['security_group_id'] == sec_grp.id:
424             out.append(SecurityGroupRule(**rule))
425     return out
426
427
428 def get_rule_by_id(neutron, sec_grp, rule_id):
429     """
430     Deletes a security group object from OpenStack
431     :param neutron: the client
432     :param sec_grp: the SNAPS SecurityGroup domain object
433     :param rule_id: the rule's ID
434     :param sec_grp: a SNAPS SecurityGroupRule domain object
435     """
436     rules = neutron.list_security_group_rules(
437         **{'security_group_id': sec_grp.id})
438     for rule in rules['security_group_rules']:
439         if rule['id'] == rule_id:
440             return SecurityGroupRule(**rule)
441     return None
442
443
444 def get_external_networks(neutron):
445     """
446     Returns a list of external OpenStack network object/dict for all external
447     networks
448     :param neutron: the client
449     :return: a list of external networks of Type SNAPS-OO domain class Network
450     """
451     out = list()
452     for network in neutron.list_networks(
453             **{'router:external': True})['networks']:
454         out.append(Network(**network))
455     return out
456
457
458 def get_floating_ips(neutron):
459     """
460     Returns all of the floating IPs
461     :param neutron: the Neutron client
462     :return: a list of SNAPS FloatingIp objects
463     """
464     out = list()
465     fips = neutron.list_floatingips()
466     for fip in fips['floatingips']:
467         out.append(FloatingIp(inst_id=fip['id'],
468                               ip=fip['floating_ip_address']))
469
470     return out
471
472
473 def create_floating_ip(neutron, ext_net_name):
474     """
475     Returns the floating IP object that was created with this call
476     :param neutron: the Neutron client
477     :param ext_net_name: the name of the external network on which to apply the
478                          floating IP address
479     :return: the SNAPS FloatingIp object
480     """
481     logger.info('Creating floating ip to external network - ' + ext_net_name)
482     ext_net = get_network(neutron, ext_net_name)
483     if ext_net:
484         fip = neutron.create_floatingip(
485             body={'floatingip':
486                   {'floating_network_id': ext_net.id}})
487
488         return FloatingIp(inst_id=fip['floatingip']['id'],
489                           ip=fip['floatingip']['floating_ip_address'])
490     else:
491         raise NeutronException(
492             'Cannot create floating IP, external network not found')
493
494
495 def get_floating_ip(neutron, floating_ip):
496     """
497     Returns a floating IP object that should be identical to the floating_ip
498     parameter
499     :param neutron: the Neutron client
500     :param floating_ip: the SNAPS FloatingIp object
501     :return: hopefully the same floating IP object input
502     """
503     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
504                  floating_ip.ip)
505     os_fip = __get_os_floating_ip(neutron, floating_ip)
506     if os_fip:
507         return FloatingIp(
508             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
509
510
511 def __get_os_floating_ip(neutron, floating_ip):
512     """
513     Returns an OpenStack floating IP object
514     parameter
515     :param neutron: the Neutron client
516     :param floating_ip: the SNAPS FloatingIp object
517     :return: hopefully the same floating IP object input
518     """
519     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
520                  floating_ip.ip)
521     fips = neutron.list_floatingips(ip=floating_ip.id)
522
523     for fip in fips['floatingips']:
524         if fip['id'] == floating_ip.id:
525             return fip
526
527
528 def delete_floating_ip(neutron, floating_ip):
529     """
530     Responsible for deleting a floating IP
531     :param neutron: the Neutron client
532     :param floating_ip: the SNAPS FloatingIp object
533     :return:
534     """
535     logger.debug('Attempting to delete existing floating ip with IP - %s',
536                  floating_ip.ip)
537     return neutron.delete_floatingip(floating_ip.id)
538
539
540 class NeutronException(Exception):
541     """
542     Exception when calls to the Keystone client cannot be served properly
543     """