6da5f8edeb0b9c608b6609de4b97f30216ebe5ff
[snaps.git] / snaps / openstack / create_router.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 snaps.openstack.create_network import PortSettings
19 from snaps.openstack.openstack_creator import OpenStackNetworkObject
20 from snaps.openstack.utils import neutron_utils, keystone_utils
21
22 __author__ = 'spisarski'
23
24 logger = logging.getLogger('OpenStackNetwork')
25
26
27 class OpenStackRouter(OpenStackNetworkObject):
28     """
29     Class responsible for managing a router in OpenStack
30     """
31
32     def __init__(self, os_creds, router_settings):
33         """
34         Constructor - all parameters are required
35         :param os_creds: The credentials to connect with OpenStack
36         :param router_settings: The settings used to create a router object
37                                 (must be an instance of the RouterSettings
38                                 class)
39         """
40         super(self.__class__, self).__init__(os_creds)
41
42         if not router_settings:
43             raise RouterCreationError('router_settings is required')
44
45         self.router_settings = router_settings
46
47         # Attributes instantiated on create()
48         self.__router = None
49         self.__internal_subnets = list()
50         self.__internal_router_interface = None
51
52         # Dict where the port object is the key and any newly created router
53         # interfaces are the value
54         self.__ports = list()
55
56     def initialize(self):
57         """
58         Loads the existing router.
59         :return: the Router domain object
60         """
61         super(self.__class__, self).initialize()
62
63         self.__router = neutron_utils.get_router(
64             self._neutron, router_settings=self.router_settings)
65
66         for internal_subnet_name in self.router_settings.internal_subnets:
67             internal_subnet = neutron_utils.get_subnet(
68                 self._neutron, subnet_name=internal_subnet_name)
69             if internal_subnet:
70                 self.__internal_subnets.append(internal_subnet)
71             else:
72                 raise RouterCreationError(
73                     'Subnet not found with name ' + internal_subnet_name)
74
75         for port_setting in self.router_settings.port_settings:
76             port = neutron_utils.get_port(
77                 self._neutron, port_settings=port_setting)
78             if port:
79                 self.__ports.append(port)
80
81         return self.__router
82
83     def create(self):
84         """
85         Responsible for creating the router.
86         :return: the Router domain object
87         """
88         self.initialize()
89
90         if not self.__router:
91             self.__router = neutron_utils.create_router(
92                 self._neutron, self._os_creds, self.router_settings)
93
94             for internal_subnet_name in self.router_settings.internal_subnets:
95                 internal_subnet = neutron_utils.get_subnet(
96                     self._neutron, subnet_name=internal_subnet_name)
97                 if internal_subnet:
98                     self.__internal_subnets.append(internal_subnet)
99                     if internal_subnet:
100                         logger.debug('Adding router to subnet...')
101                         router_intf = neutron_utils.add_interface_router(
102                             self._neutron, self.__router,
103                             subnet=internal_subnet)
104                         self.__internal_router_interface = router_intf
105                 else:
106                     raise RouterCreationError(
107                         'Subnet not found with name ' + internal_subnet_name)
108
109             for port_setting in self.router_settings.port_settings:
110                 port = neutron_utils.get_port(
111                     self._neutron, port_settings=port_setting)
112                 logger.info(
113                     'Retrieved port %s for router - %s', port_setting.name,
114                     self.router_settings.name)
115                 if port:
116                     self.__ports.append(port)
117
118                 if not port:
119                     port = neutron_utils.create_port(
120                         self._neutron, self._os_creds, port_setting)
121                     if port:
122                         logger.info(
123                             'Created port %s for router - %s',
124                             port_setting.name,
125                             self.router_settings.name)
126                         self.__ports.append(port)
127                         neutron_utils.add_interface_router(self._neutron,
128                                                            self.__router,
129                                                            port=port)
130                     else:
131                         raise RouterCreationError(
132                             'Error creating port with name - '
133                             + port_setting.name)
134
135         self.__router = neutron_utils.get_router_by_id(
136             self._neutron, self.__router.id)
137         return self.__router
138
139     def clean(self):
140         """
141         Removes and deletes all items created in reverse order.
142         """
143         for port in self.__ports:
144             logger.info(
145                 'Removing router interface from router %s and port %s',
146                 self.router_settings.name, port.name)
147             try:
148                 neutron_utils.remove_interface_router(self._neutron,
149                                                       self.__router, port=port)
150             except NotFound:
151                 pass
152         self.__ports = list()
153
154         for internal_subnet in self.__internal_subnets:
155             logger.info(
156                 'Removing router interface from router %s and subnet %s',
157                 self.router_settings.name, internal_subnet.name)
158             try:
159                 neutron_utils.remove_interface_router(self._neutron,
160                                                       self.__router,
161                                                       subnet=internal_subnet)
162             except NotFound:
163                 pass
164         self.__internal_subnets = list()
165
166         if self.__router:
167             logger.info('Removing router ' + self.router_settings.name)
168             try:
169                 neutron_utils.delete_router(self._neutron, self.__router)
170             except NotFound:
171                 pass
172             self.__router = None
173
174     def get_router(self):
175         """
176         Returns the OpenStack router object
177         :return:
178         """
179         return self.__router
180
181     def get_internal_router_interface(self):
182         """
183         Returns the OpenStack internal router interface object
184         :return:
185         """
186         return self.__internal_router_interface
187
188
189 class RouterCreationError(Exception):
190     """
191     Exception to be thrown when an router instance cannot be created
192     """
193
194
195 class RouterSettings:
196     """
197     Class representing a router configuration
198     """
199
200     def __init__(self, **kwargs):
201         """
202         Constructor - all parameters are optional
203         :param name: The router name.
204         :param project_name: The name of the project who owns the network. Only
205                              administrative users can specify a project ID
206                              other than their own. You cannot change this value
207                              through authorization policies.
208         :param external_gateway: Name of the external network to which to route
209         :param admin_state_up: The administrative status of the router.
210                                True = up / False = down (default True)
211         :param internal_subnets: List of subnet names to which to connect this
212                                  router for Floating IP purposes
213         :param port_settings: List of PortSettings objects
214         :return:
215         """
216         self.name = kwargs.get('name')
217         self.project_name = kwargs.get('project_name')
218         self.external_gateway = kwargs.get('external_gateway')
219
220         self.admin_state_up = kwargs.get('admin_state_up', True)
221         self.enable_snat = kwargs.get('enable_snat')
222         if kwargs.get('internal_subnets'):
223             self.internal_subnets = kwargs['internal_subnets']
224         else:
225             self.internal_subnets = list()
226
227         self.port_settings = list()
228         if kwargs.get('interfaces', kwargs.get('port_settings')):
229             interfaces = kwargs.get('interfaces', kwargs.get('port_settings'))
230             for interface in interfaces:
231                 if isinstance(interface, PortSettings):
232                     self.port_settings.append(interface)
233                 else:
234                     self.port_settings.append(
235                         PortSettings(**interface['port']))
236
237         if not self.name:
238             raise RouterSettingsError('Name is required')
239
240     def dict_for_neutron(self, neutron, os_creds):
241         """
242         Returns a dictionary object representing this object.
243         This is meant to be converted into JSON designed for use by the Neutron
244         API
245
246         TODO - expand automated testing to exercise all parameters
247         :param neutron: The neutron client to retrieve external network
248                         information if necessary
249         :param os_creds: The OpenStack credentials
250         :return: the dictionary object
251         """
252         out = dict()
253         ext_gw = dict()
254
255         if self.name:
256             out['name'] = self.name
257         if self.project_name:
258             keystone = keystone_utils.keystone_client(os_creds)
259             project = keystone_utils.get_project(
260                 keystone=keystone, project_name=self.project_name)
261             project_id = None
262             if project:
263                 project_id = project.id
264             if project_id:
265                 out['tenant_id'] = project_id
266             else:
267                 raise RouterSettingsError(
268                     'Could not find project ID for project named - ' +
269                     self.project_name)
270         if self.admin_state_up is not None:
271             out['admin_state_up'] = self.admin_state_up
272         if self.external_gateway:
273             ext_net = neutron_utils.get_network(
274                 neutron, network_name=self.external_gateway)
275             if ext_net:
276                 ext_gw['network_id'] = ext_net.id
277                 out['external_gateway_info'] = ext_gw
278             else:
279                 raise RouterSettingsError(
280                     'Could not find the external network named - ' +
281                     self.external_gateway)
282
283         return {'router': out}
284
285
286 class RouterSettingsError(Exception):
287     """
288     Exception to be thrown when router settings attributes are incorrect
289     """