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