Created domain classes for security groups.
[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 SecurityGroup, SecurityGroupRule
21 from snaps.domain.vm_inst import FloatingIp
22 from snaps.openstack.utils import keystone_utils
23
24 __author__ = 'spisarski'
25
26 logger = logging.getLogger('neutron_utils')
27
28 """
29 Utilities for basic neutron API calls
30 """
31
32
33 def neutron_client(os_creds):
34     """
35     Instantiates and returns a client for communications with OpenStack's
36     Neutron server
37     :param os_creds: the credentials for connecting to the OpenStack remote API
38     :return: the client object
39     """
40     return Client(api_version=os_creds.network_api_version,
41                   session=keystone_utils.keystone_session(os_creds))
42
43
44 def create_network(neutron, os_creds, network_settings):
45     """
46     Creates a network for OpenStack
47     :param neutron: the client
48     :param os_creds: the OpenStack credentials
49     :param network_settings: A dictionary containing the network configuration
50                              and is responsible for creating the network
51                             request JSON body
52     :return: the network object
53     """
54     if neutron and network_settings:
55         logger.info('Creating network with name ' + network_settings.name)
56         json_body = network_settings.dict_for_neutron(os_creds)
57         return neutron.create_network(body=json_body)
58     else:
59         logger.error("Failed to create network")
60         raise Exception
61
62
63 def delete_network(neutron, network):
64     """
65     Deletes a network for OpenStack
66     :param neutron: the client
67     :param network: the network object
68     """
69     if neutron and network:
70         logger.info('Deleting network with name ' + network['network']['name'])
71         neutron.delete_network(network['network']['id'])
72
73
74 def get_network(neutron, network_name, project_id=None):
75     """
76     Returns an object (dictionary) of the first network found with a given name
77     and project_id (if included)
78     :param neutron: the client
79     :param network_name: the name of the network to retrieve
80     :param project_id: the id of the network's project
81     :return:
82     """
83     net_filter = dict()
84     if network_name:
85         net_filter['name'] = network_name
86     if project_id:
87         net_filter['project_id'] = project_id
88
89     networks = neutron.list_networks(**net_filter)
90     for network, netInsts in networks.items():
91         for inst in netInsts:
92             if inst.get('name') == network_name:
93                 if project_id and inst.get('project_id') == project_id:
94                     return {'network': inst}
95                 else:
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:
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: the subnet 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: the subnet object
142     """
143     if neutron and subnet:
144         logger.info('Deleting subnet with name ' + subnet['subnet']['name'])
145         neutron.delete_subnet(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:
154     """
155     subnets = neutron.list_subnets(**{'name': subnet_name})
156     for subnet, subnetInst in subnets.items():
157         for inst in subnetInst:
158             if inst.get('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: the router 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         return neutron.create_router(json_body)
177     else:
178         logger.error("Failed to create router.")
179         raise Exception
180
181
182 def delete_router(neutron, router):
183     """
184     Deletes a router for OpenStack
185     :param neutron: the client
186     :param router: the router object
187     """
188     if neutron and router:
189         logger.info('Deleting router with name - ' + router['router']['name'])
190         neutron.delete_router(router=router['router']['id'])
191         return True
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:
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 ' +
225                     router['router']['name'])
226         return neutron.add_interface_router(
227             router=router['router']['id'],
228             body=__create_port_json_body(subnet, port))
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 router 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['router']['name'])
246             neutron.remove_interface_router(
247                 router=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['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 port 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     return neutron.create_port(body=json_body)
289
290
291 def delete_port(neutron, port):
292     """
293     Removes an OpenStack port
294     :param neutron: the client
295     :param port: the port object
296     :return:
297     """
298     logger.info('Deleting port with name ' + port['port']['name'])
299     neutron.delete_port(port['port']['id'])
300
301
302 def get_port_by_name(neutron, port_name):
303     """
304     Returns the first port object (dictionary) found with a given name
305     :param neutron: the client
306     :param port_name: the name of the port to retrieve
307     :return:
308     """
309     ports = neutron.list_ports(**{'name': port_name})
310     for port in ports['ports']:
311         if port['name'] == port_name:
312             return {'port': port}
313     return None
314
315
316 def create_security_group(neutron, keystone, sec_grp_settings):
317     """
318     Creates a security group object in OpenStack
319     :param neutron: the Neutron client
320     :param keystone: the Keystone client
321     :param sec_grp_settings: the security group settings
322     :return: the security group object
323     """
324     logger.info('Creating security group with name - %s',
325                 sec_grp_settings.name)
326     os_group = neutron.create_security_group(
327         sec_grp_settings.dict_for_neutron(keystone))
328     return SecurityGroup(
329         id=os_group['security_group']['id'],
330         name=os_group['security_group']['name'],
331         project_id=os_group['security_group'].get(
332             'project_id', os_group['security_group'].get('tenant_id')))
333
334
335 def delete_security_group(neutron, sec_grp):
336     """
337     Deletes a security group object from OpenStack
338     :param neutron: the client
339     :param sec_grp: the SNAPS SecurityGroup object to delete
340     """
341     logger.info('Deleting security group with name - %s', sec_grp.name)
342     neutron.delete_security_group(sec_grp.id)
343
344
345 def get_security_group(neutron, name):
346     """
347     Returns the first security group object of the given name else None
348     :param neutron: the client
349     :param name: the name of security group object to retrieve
350     """
351     logger.info('Retrieving security group with name - ' + name)
352
353     groups = neutron.list_security_groups(**{'name': name})
354     for group in groups['security_groups']:
355         if group['name'] == name:
356             return SecurityGroup(
357                 id=group['id'], name=group['name'],
358                 project_id=group.get('project_id', group.get('tenant_id')))
359     return None
360
361
362 def get_security_group_by_id(neutron, sec_grp_id):
363     """
364     Returns the first security group object of the given name else None
365     :param neutron: the client
366     :param sec_grp_id: the id of the security group to retrieve
367     """
368     logger.info('Retrieving security group with ID - ' + sec_grp_id)
369
370     groups = neutron.list_security_groups(**{'id': sec_grp_id})
371     for group in groups['security_groups']:
372         if group['id'] == sec_grp_id:
373             return SecurityGroup(
374                 id=group['id'], name=group['name'],
375                 project_id=group.get('project_id', group.get('tenant_id')))
376     return None
377
378
379 def create_security_group_rule(neutron, sec_grp_rule_settings):
380     """
381     Creates a security group object in OpenStack
382     :param neutron: the client
383     :param sec_grp_rule_settings: the security group rule settings
384     :return: the security group object
385     """
386     logger.info('Creating security group to security group - %s',
387                 sec_grp_rule_settings.sec_grp_name)
388     os_rule = neutron.create_security_group_rule(
389         sec_grp_rule_settings.dict_for_neutron(neutron))
390     return SecurityGroupRule(**os_rule['security_group_rule'])
391
392
393 def delete_security_group_rule(neutron, sec_grp_rule):
394     """
395     Deletes a security group object from OpenStack
396     :param neutron: the client
397     :param sec_grp_rule: the SNAPS SecurityGroupRule object to delete
398     """
399     logger.info('Deleting security group rule with ID - %s',
400                 sec_grp_rule.id)
401     neutron.delete_security_group_rule(sec_grp_rule.id)
402
403
404 def get_rules_by_security_group(neutron, sec_grp):
405     """
406     Retrieves all of the rules for a given security group
407     :param neutron: the client
408     :param sec_grp: the SNAPS SecurityGroup object
409     """
410     logger.info('Retrieving security group rules associate with the '
411                 'security group - %s', sec_grp.name)
412     out = list()
413     rules = neutron.list_security_group_rules(
414         **{'security_group_id': sec_grp.id})
415     for rule in rules['security_group_rules']:
416         if rule['security_group_id'] == sec_grp.id:
417             out.append(SecurityGroupRule(**rule))
418     return out
419
420
421 def get_rule_by_id(neutron, sec_grp, rule_id):
422     """
423     Deletes a security group object from OpenStack
424     :param neutron: the client
425     :param sec_grp: the SNAPS SecurityGroup domain object
426     :param rule_id: the rule's ID
427     """
428     rules = neutron.list_security_group_rules(
429         **{'security_group_id': sec_grp.id})
430     for rule in rules['security_group_rules']:
431         if rule['id'] == rule_id:
432             return SecurityGroupRule(**rule)
433     return None
434
435
436 def get_external_networks(neutron):
437     """
438     Returns a list of external OpenStack network object/dict for all external
439     networks
440     :param neutron: the client
441     :return: a list of external networks (empty list if none configured)
442     """
443     out = list()
444     for network in neutron.list_networks(
445             **{'router:external': True})['networks']:
446         out.append({'network': network})
447     return out
448
449
450 def get_floating_ips(neutron):
451     """
452     Returns all of the floating IPs
453     :param neutron: the Neutron client
454     :return: a list of SNAPS FloatingIp objects
455     """
456     out = list()
457     fips = neutron.list_floatingips()
458     for fip in fips['floatingips']:
459         out.append(FloatingIp(inst_id=fip['id'],
460                               ip=fip['floating_ip_address']))
461
462     return out
463
464
465 def create_floating_ip(neutron, ext_net_name):
466     """
467     Returns the floating IP object that was created with this call
468     :param neutron: the Neutron client
469     :param ext_net_name: the name of the external network on which to apply the
470                          floating IP address
471     :return: the SNAPS FloatingIp object
472     """
473     logger.info('Creating floating ip to external network - ' + ext_net_name)
474     ext_net = get_network(neutron, ext_net_name)
475     if ext_net:
476         fip = neutron.create_floatingip(
477             body={'floatingip':
478                   {'floating_network_id': ext_net['network']['id']}})
479
480         return FloatingIp(inst_id=fip['floatingip']['id'],
481                           ip=fip['floatingip']['floating_ip_address'])
482     else:
483         raise Exception('Cannot create floating IP, '
484                         'external network not found')
485
486
487 def get_floating_ip(neutron, floating_ip):
488     """
489     Returns a floating IP object that should be identical to the floating_ip
490     parameter
491     :param neutron: the Neutron client
492     :param floating_ip: the SNAPS FloatingIp object
493     :return: hopefully the same floating IP object input
494     """
495     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
496                  floating_ip.ip)
497     os_fip = get_os_floating_ip(neutron, floating_ip)
498     if os_fip:
499         return FloatingIp(
500             inst_id=os_fip['id'], ip=os_fip['floating_ip_address'])
501
502
503 def get_os_floating_ip(neutron, floating_ip):
504     """
505     Returns an OpenStack floating IP object
506     parameter
507     :param neutron: the Neutron client
508     :param floating_ip: the SNAPS FloatingIp object
509     :return: hopefully the same floating IP object input
510     """
511     logger.debug('Attempting to retrieve existing floating ip with IP - %s',
512                  floating_ip.ip)
513     fips = neutron.list_floatingips(ip=floating_ip.id)
514
515     for fip in fips['floatingips']:
516         if fip['id'] == floating_ip.id:
517             return fip
518
519
520 def delete_floating_ip(neutron, floating_ip):
521     """
522     Responsible for deleting a floating IP
523     :param neutron: the Neutron client
524     :param floating_ip: the SNAPS FloatingIp object
525     :return:
526     """
527     logger.debug('Attempting to delete existing floating ip with IP - %s',
528                  floating_ip.ip)
529     return neutron.delete_floatingip(floating_ip.id)