Last task necessary to stop users from easily obtaining OS objects.
[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         logger.error("Failed to create network")
63         raise Exception
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_name, project_id=None):
78     """
79     Returns an object (dictionary) of the first network found with a given name
80     and project_id (if included)
81     :param neutron: the client
82     :param network_name: the name of the network to retrieve
83     :param project_id: the id of the network's project
84     :return: a SNAPS-OO Network domain object
85     """
86     net_filter = dict()
87     if network_name:
88         net_filter['name'] = network_name
89     if project_id:
90         net_filter['project_id'] = project_id
91
92     networks = neutron.list_networks(**net_filter)
93     for network, netInsts in networks.items():
94         for inst in netInsts:
95             if inst.get('name') == network_name:
96                 return Network(**inst)
97     return None
98
99
100 def get_network_by_id(neutron, network_id):
101     """
102     Returns the network object (dictionary) with the given ID
103     :param neutron: the client
104     :param network_id: the id of the network to retrieve
105     :return: a SNAPS-OO Network domain object
106     """
107     networks = neutron.list_networks(**{'id': network_id})
108     for network, netInsts in networks.items():
109         for inst in netInsts:
110             if inst.get('id') == network_id:
111                 return Network(**inst)
112     return None
113
114
115 def create_subnet(neutron, subnet_settings, os_creds, network=None):
116     """
117     Creates a network subnet for OpenStack
118     :param neutron: the client
119     :param network: the network object
120     :param subnet_settings: A dictionary containing the subnet configuration
121                             and is responsible for creating the subnet request
122                             JSON body
123     :param os_creds: the OpenStack credentials
124     :return: a SNAPS-OO Subnet domain object
125     """
126     if neutron and network and subnet_settings:
127         json_body = {'subnets': [subnet_settings.dict_for_neutron(
128             os_creds, network=network)]}
129         logger.info('Creating subnet with name ' + subnet_settings.name)
130         subnets = neutron.create_subnet(body=json_body)
131         return Subnet(**subnets['subnets'][0])
132     else:
133         logger.error("Failed to create subnet.")
134         raise Exception
135
136
137 def delete_subnet(neutron, subnet):
138     """
139     Deletes a network subnet for OpenStack
140     :param neutron: the client
141     :param subnet: a SNAPS-OO Subnet domain object
142     """
143     if neutron and subnet:
144         logger.info('Deleting subnet with name ' + subnet.name)
145         neutron.delete_subnet(subnet.id)
146
147
148 def get_subnet_by_name(neutron, subnet_name):
149     """
150     Returns the first subnet object (dictionary) found with a given name
151     :param neutron: the client
152     :param subnet_name: the name of the network to retrieve
153     :return: a SNAPS-OO Subnet domain object
154     """
155     subnets = neutron.list_subnets(**{'name': subnet_name})
156     for subnet, subnetInst in subnets.items():
157         for inst in subnetInst:
158             if inst['name'] == subnet_name:
159                 return Subnet(**inst)
160     return None
161
162
163 def create_router(neutron, os_creds, router_settings):
164     """
165     Creates a router for OpenStack
166     :param neutron: the client
167     :param os_creds: the OpenStack credentials
168     :param router_settings: A dictionary containing the router configuration
169                             and is responsible for creating the subnet request
170                             JSON body
171     :return: a SNAPS-OO Router domain object
172     """
173     if neutron:
174         json_body = router_settings.dict_for_neutron(neutron, os_creds)
175         logger.info('Creating router with name - ' + router_settings.name)
176         os_router = neutron.create_router(json_body)
177         return Router(**os_router['router'])
178     else:
179         logger.error("Failed to create router.")
180         raise Exception
181
182
183 def delete_router(neutron, router):
184     """
185     Deletes a router for OpenStack
186     :param neutron: the client
187     :param router: a SNAPS-OO Router domain object
188     """
189     if neutron and router:
190         logger.info('Deleting router with name - ' + router.name)
191         neutron.delete_router(router=router.id)
192
193
194 def get_router_by_name(neutron, router_name):
195     """
196     Returns the first router object (dictionary) found with a given name
197     :param neutron: the client
198     :param router_name: the name of the network to retrieve
199     :return: a SNAPS-OO Router domain object
200     """
201     routers = neutron.list_routers(**{'name': router_name})
202     for router, routerInst in routers.items():
203         for inst in routerInst:
204             if inst.get('name') == router_name:
205                 return Router(**inst)
206     return None
207
208
209 def add_interface_router(neutron, router, subnet=None, port=None):
210     """
211     Adds an interface router for OpenStack for either a subnet or port.
212     Exception will be raised if requesting for both.
213     :param neutron: the client
214     :param router: the router object
215     :param subnet: the subnet object
216     :param port: the port object
217     :return: the interface router object
218     """
219     if subnet and port:
220         raise Exception('Cannot add interface to the router. Both subnet and '
221                         'port were sent in. Either or please.')
222
223     if neutron and router and (router or subnet):
224         logger.info('Adding interface to router with name ' + router.name)
225         os_intf_router = neutron.add_interface_router(
226             router=router.id, body=__create_port_json_body(subnet, port))
227         return InterfaceRouter(**os_intf_router)
228     else:
229         raise Exception('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 Exception('Cannot create JSON body with both subnet and port')
267     if not subnet and not port:
268         raise Exception('Cannot create JSON body without subnet or port')
269
270     if subnet:
271         return {"subnet_id": subnet.id}
272     else:
273         return {"port_id": port.id}
274
275
276 def create_port(neutron, os_creds, port_settings):
277     """
278     Creates a port for OpenStack
279     :param neutron: the client
280     :param os_creds: the OpenStack credentials
281     :param port_settings: the settings object for port configuration
282     :return: the SNAPS-OO Port domain object
283     """
284     json_body = port_settings.dict_for_neutron(neutron, os_creds)
285     logger.info('Creating port for network with name - %s',
286                 port_settings.network_name)
287     os_port = neutron.create_port(body=json_body)['port']
288     return Port(name=os_port['name'], id=os_port['id'],
289                 ips=os_port['fixed_ips'],
290                 mac_address=os_port['mac_address'],
291                 allowed_address_pairs=os_port['allowed_address_pairs'])
292
293
294 def delete_port(neutron, port):
295     """
296     Removes an OpenStack port
297     :param neutron: the client
298     :param port: the SNAPS-OO Port domain object
299     """
300     logger.info('Deleting port with name ' + port.name)
301     neutron.delete_port(port.id)
302
303
304 def get_port_by_name(neutron, port_name):
305     """
306     Returns the first port object (dictionary) found with a given name
307     :param neutron: the client
308     :param port_name: the name of the port to retrieve
309     :return: a SNAPS-OO Port domain object
310     """
311     ports = neutron.list_ports(**{'name': port_name})
312     for port in ports['ports']:
313         if port['name'] == port_name:
314             return Port(name=port['name'], id=port['id'],
315                         ips=port['fixed_ips'], mac_address=port['mac_address'])
316     return None
317
318
319 def create_security_group(neutron, keystone, sec_grp_settings):
320     """
321     Creates a security group object in OpenStack
322     :param neutron: the Neutron client
323     :param keystone: the Keystone client
324     :param sec_grp_settings: the security group settings
325     :return: a SNAPS-OO SecurityGroup domain object
326     """
327     logger.info('Creating security group with name - %s',
328                 sec_grp_settings.name)
329     os_group = neutron.create_security_group(
330         sec_grp_settings.dict_for_neutron(keystone))
331     return SecurityGroup(
332         id=os_group['security_group']['id'],
333         name=os_group['security_group']['name'],
334         project_id=os_group['security_group'].get(
335             'project_id', os_group['security_group'].get('tenant_id')))
336
337
338 def delete_security_group(neutron, sec_grp):
339     """
340     Deletes a security group object from OpenStack
341     :param neutron: the client
342     :param sec_grp: the SNAPS SecurityGroup object to delete
343     """
344     logger.info('Deleting security group with name - %s', sec_grp.name)
345     neutron.delete_security_group(sec_grp.id)
346
347
348 def get_security_group(neutron, name):
349     """
350     Returns the first security group object of the given name else None
351     :param neutron: the client
352     :param name: the name of security group object to retrieve
353     :return: a SNAPS-OO SecurityGroup domain object or None if not found
354     """
355     logger.info('Retrieving security group with name - ' + name)
356
357     groups = neutron.list_security_groups(**{'name': name})
358     for group in groups['security_groups']:
359         if group['name'] == name:
360             return SecurityGroup(
361                 id=group['id'], name=group['name'],
362                 project_id=group.get('project_id', group.get('tenant_id')))
363     return None
364
365
366 def get_security_group_by_id(neutron, sec_grp_id):
367     """
368     Returns the first security group object of the given name else None
369     :param neutron: the client
370     :param sec_grp_id: the id of the security group to retrieve
371     :return: a SNAPS-OO SecurityGroup domain object or None if not found
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: a SNAPS-OO SecurityGroupRule domain 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: a list of SNAPS SecurityGroupRule domain objects
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     :param sec_grp: a SNAPS SecurityGroupRule domain object
433     """
434     rules = neutron.list_security_group_rules(
435         **{'security_group_id': sec_grp.id})
436     for rule in rules['security_group_rules']:
437         if rule['id'] == rule_id:
438             return SecurityGroupRule(**rule)
439     return None
440
441
442 def get_external_networks(neutron):
443     """
444     Returns a list of external OpenStack network object/dict for all external
445     networks
446     :param neutron: the client
447     :return: a list of external networks of Type SNAPS-OO domain class Network
448     """
449     out = list()
450     for network in neutron.list_networks(
451             **{'router:external': True})['networks']:
452         out.append(Network(**network))
453     return out
454
455
456 def get_floating_ips(neutron):
457     """
458     Returns all of the floating IPs
459     :param neutron: the Neutron client
460     :return: a list of SNAPS FloatingIp objects
461     """
462     out = list()
463     fips = neutron.list_floatingips()
464     for fip in fips['floatingips']:
465         out.append(FloatingIp(inst_id=fip['id'],
466                               ip=fip['floating_ip_address']))
467
468     return out
469
470
471 def create_floating_ip(neutron, ext_net_name):
472     """
473     Returns the floating IP object that was created with this call
474     :param neutron: the Neutron client
475     :param ext_net_name: the name of the external network on which to apply the
476                          floating IP address
477     :return: the SNAPS FloatingIp object
478     """
479     logger.info('Creating floating ip to external network - ' + ext_net_name)
480     ext_net = get_network(neutron, ext_net_name)
481     if ext_net:
482         fip = neutron.create_floatingip(
483             body={'floatingip':
484                   {'floating_network_id': ext_net.id}})
485
486         return FloatingIp(inst_id=fip['floatingip']['id'],
487                           ip=fip['floatingip']['floating_ip_address'])
488     else:
489         raise Exception('Cannot create floating IP, '
490                         'external network not found')
491
492
493 def get_floating_ip(neutron, floating_ip):
494     """
495     Returns a floating IP object that should be identical to the floating_ip
496     parameter
497     :param neutron: the Neutron client
498     :param floating_ip: the SNAPS FloatingIp object
499     :return: hopefully the same floating IP object input
500     """
501     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
502                  floating_ip.ip)
503     os_fip = __get_os_floating_ip(neutron, floating_ip)
504     if os_fip:
505         return FloatingIp(
506             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
507
508
509 def __get_os_floating_ip(neutron, floating_ip):
510     """
511     Returns an OpenStack floating IP object
512     parameter
513     :param neutron: the Neutron client
514     :param floating_ip: the SNAPS FloatingIp object
515     :return: hopefully the same floating IP object input
516     """
517     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
518                  floating_ip.ip)
519     fips = neutron.list_floatingips(ip=floating_ip.id)
520
521     for fip in fips['floatingips']:
522         if fip['id'] == floating_ip.id:
523             return fip
524
525
526 def delete_floating_ip(neutron, floating_ip):
527     """
528     Responsible for deleting a floating IP
529     :param neutron: the Neutron client
530     :param floating_ip: the SNAPS FloatingIp object
531     :return:
532     """
533     logger.debug('Attempting to delete existing floating ip with IP - %s',
534                  floating_ip.ip)
535     return neutron.delete_floatingip(floating_ip.id)