| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 1 | # Copyright (c) 2015 SONATA-NFV and Paderborn University |
| 2 | # 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 | # |
| 16 | # Neither the name of the SONATA-NFV, Paderborn University |
| 17 | # nor the names of its contributors may be used to endorse or promote |
| 18 | # products derived from this software without specific prior written |
| 19 | # permission. |
| 20 | # |
| 21 | # This work has been performed in the framework of the SONATA project, |
| 22 | # funded by the European Commission under Grant number 671517 through |
| 23 | # the Horizon 2020 and 5G-PPP programmes. The authors would like to |
| 24 | # acknowledge the contributions of their colleagues of the SONATA |
| 25 | # partner consortium (www.sonata-nfv.eu). |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 26 | # TODO remove when print is no longer needed for debugging |
| 27 | from __future__ import print_function |
| 28 | from emuvim.api.openstack.resources.router import Router |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 29 | from datetime import datetime |
| 30 | import re |
| 31 | import sys |
| 32 | import uuid |
| 33 | import logging |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 34 | import emuvim.api.openstack.ip_handler as IP |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 35 | |
| 36 | |
| peusterm | bbf4f74 | 2017-06-19 11:22:11 +0200 | [diff] [blame] | 37 | LOG = logging.getLogger("api.openstack.heat.parser") |
| 38 | |
| 39 | |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 40 | class HeatParser: |
| 41 | """ |
| 42 | The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu. |
| 43 | """ |
| 44 | |
| 45 | def __init__(self, compute): |
| 46 | self.description = None |
| 47 | self.parameter_groups = None |
| 48 | self.parameters = None |
| 49 | self.resources = None |
| 50 | self.outputs = None |
| 51 | self.compute = compute |
| 52 | self.bufferResource = list() |
| 53 | |
| 54 | def parse_input(self, input_dict, stack, dc_label, stack_update=False): |
| 55 | """ |
| 56 | It will parse the input dictionary into the corresponding classes, which are then stored within the stack. |
| 57 | |
| 58 | :param input_dict: Dictionary with the template version and resources. |
| 59 | :type input_dict: ``dict`` |
| 60 | :param stack: Reference of the stack that should finally contain all created classes. |
| 61 | :type stack: :class:`heat.resources.stack` |
| 62 | :param dc_label: String that contains the label of the used data center. |
| 63 | :type dc_label: ``str`` |
| 64 | :param stack_update: Specifies if a new stack will be created or a older one will be updated |
| 65 | :type stack_update: ``bool`` |
| 66 | :return: * *True*: If the template version is supported and all resources could be created. |
| 67 | * *False*: Else |
| 68 | :rtype: ``bool`` |
| 69 | """ |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 70 | if not self.check_template_version( |
| 71 | str(input_dict['heat_template_version'])): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 72 | print('Unsupported template version: ' + input_dict['heat_template_version'], file=sys.stderr) |
| 73 | return False |
| 74 | |
| 75 | self.description = input_dict.get('description', None) |
| 76 | self.parameter_groups = input_dict.get('parameter_groups', None) |
| 77 | self.parameters = input_dict.get('parameters', None) |
| 78 | self.resources = input_dict.get('resources', None) |
| 79 | self.outputs = input_dict.get('outputs', None) |
| 80 | # clear bufferResources |
| 81 | self.bufferResource = list() |
| 82 | |
| 83 | for resource in self.resources.values(): |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 84 | self.handle_resource(resource, stack, dc_label, |
| 85 | stack_update=stack_update) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 86 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 87 | # This loop tries to create all classes which had unresolved |
| 88 | # dependencies. |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 89 | unresolved_resources_last_round = len(self.bufferResource) + 1 |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 90 | while len(self.bufferResource) > 0 and unresolved_resources_last_round > len( |
| 91 | self.bufferResource): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 92 | unresolved_resources_last_round = len(self.bufferResource) |
| 93 | number_of_items = len(self.bufferResource) |
| 94 | while number_of_items > 0: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 95 | self.handle_resource(self.bufferResource.pop( |
| 96 | 0), stack, dc_label, stack_update=stack_update) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 97 | number_of_items -= 1 |
| 98 | |
| 99 | if len(self.bufferResource) > 0: |
| 100 | print(str(len(self.bufferResource)) + |
| peusterm | 7ab1dbe | 2017-06-14 14:56:37 +0200 | [diff] [blame] | 101 | ' classes of the HOT could not be created, because the dependencies could not be found.') |
| 102 | print("the problem classes are:") |
| 103 | for br in self.bufferResource: |
| 104 | print("class: %s" % str(br)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 105 | return False |
| 106 | return True |
| 107 | |
| 108 | def handle_resource(self, resource, stack, dc_label, stack_update=False): |
| 109 | """ |
| 110 | This function will take a resource (from a heat template) and determines which type it is and creates |
| 111 | the corresponding class, with its required parameters, for further calculations (like deploying the stack). |
| 112 | If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource |
| 113 | within the 'self.bufferResource' list. |
| 114 | |
| 115 | :param resource: Dict which contains all important informations about the type and parameters. |
| 116 | :type resource: ``dict`` |
| 117 | :param stack: Reference of the stack that should finally contain the created class. |
| 118 | :type stack: :class:`heat.resources.stack` |
| 119 | :param dc_label: String that contains the label of the used data center |
| 120 | :type dc_label: ``str`` |
| 121 | :param stack_update: Specifies if a new stack will be created or a older one will be updated |
| 122 | :type stack_update: ``bool`` |
| 123 | :return: void |
| 124 | :rtype: ``None`` |
| 125 | """ |
| 126 | if "OS::Neutron::Net" in resource['type']: |
| 127 | try: |
| 128 | net_name = resource['properties']['name'] |
| 129 | if net_name not in stack.nets: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 130 | stack.nets[net_name] = self.compute.create_network( |
| 131 | net_name, True) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 132 | |
| 133 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 134 | LOG.warning('Could not create Net: ' + str(e)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 135 | return |
| 136 | |
| 137 | if 'OS::Neutron::Subnet' in resource['type'] and "Net" not in resource['type']: |
| 138 | try: |
| 139 | net_name = resource['properties']['network']['get_resource'] |
| 140 | if net_name not in stack.nets: |
| 141 | net = self.compute.create_network(net_name, stack_update) |
| 142 | stack.nets[net_name] = net |
| 143 | else: |
| 144 | net = stack.nets[net_name] |
| 145 | |
| 146 | net.subnet_name = resource['properties']['name'] |
| 147 | if 'gateway_ip' in resource['properties']: |
| 148 | net.gateway_ip = resource['properties']['gateway_ip'] |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 149 | net.subnet_id = resource['properties'].get( |
| 150 | 'id', str(uuid.uuid4())) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 151 | net.subnet_creation_time = str(datetime.now()) |
| 152 | if not stack_update: |
| 153 | net.set_cidr(IP.get_new_cidr(net.subnet_id)) |
| 154 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 155 | LOG.warning('Could not create Subnet: ' + str(e)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 156 | return |
| 157 | |
| 158 | if 'OS::Neutron::Port' in resource['type']: |
| 159 | try: |
| 160 | port_name = resource['properties']['name'] |
| 161 | if port_name not in stack.ports: |
| 162 | port = self.compute.create_port(port_name, stack_update) |
| 163 | stack.ports[port_name] = port |
| 164 | else: |
| 165 | port = stack.ports[port_name] |
| 166 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 167 | if str(resource['properties']['network'] |
| 168 | ['get_resource']) in stack.nets: |
| 169 | net = stack.nets[resource['properties'] |
| 170 | ['network']['get_resource']] |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 171 | if net.subnet_id is not None: |
| 172 | port.net_name = net.name |
| 173 | port.ip_address = net.get_new_ip_address(port.name) |
| 174 | return |
| 175 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 176 | LOG.warning('Could not create Port: ' + str(e)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 177 | self.bufferResource.append(resource) |
| 178 | return |
| 179 | |
| 180 | if 'OS::Nova::Server' in resource['type']: |
| 181 | try: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 182 | compute_name = str(dc_label) + '_' + str(stack.stack_name) + \ |
| 183 | '_' + str(resource['properties']['name']) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 184 | shortened_name = str(dc_label) + '_' + str(stack.stack_name) + '_' + \ |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 185 | self.shorten_server_name( |
| 186 | str(resource['properties']['name']), stack) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 187 | nw_list = resource['properties']['networks'] |
| 188 | |
| 189 | if shortened_name not in stack.servers: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 190 | server = self.compute.create_server( |
| 191 | shortened_name, stack_update) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 192 | stack.servers[shortened_name] = server |
| 193 | else: |
| 194 | server = stack.servers[shortened_name] |
| 195 | |
| 196 | server.full_name = compute_name |
| 197 | server.template_name = str(resource['properties']['name']) |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 198 | server.command = resource['properties'].get( |
| 199 | 'command', '/bin/sh') |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 200 | server.image = resource['properties']['image'] |
| 201 | server.flavor = resource['properties']['flavor'] |
| 202 | |
| 203 | for port in nw_list: |
| 204 | port_name = port['port']['get_resource'] |
| 205 | # just create a port |
| 206 | # we don't know which network it belongs to yet, but the resource will appear later in a valid |
| 207 | # template |
| 208 | if port_name not in stack.ports: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 209 | stack.ports[port_name] = self.compute.create_port( |
| 210 | port_name, stack_update) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 211 | server.port_names.append(port_name) |
| 212 | return |
| 213 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 214 | LOG.warning('Could not create Server: ' + str(e)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 215 | return |
| 216 | |
| 217 | if 'OS::Neutron::RouterInterface' in resource['type']: |
| 218 | try: |
| 219 | router_name = None |
| 220 | subnet_name = resource['properties']['subnet']['get_resource'] |
| 221 | |
| 222 | if 'get_resource' in resource['properties']['router']: |
| 223 | router_name = resource['properties']['router']['get_resource'] |
| 224 | else: |
| 225 | router_name = resource['properties']['router'] |
| 226 | |
| 227 | if router_name not in stack.routers: |
| 228 | stack.routers[router_name] = Router(router_name) |
| 229 | |
| 230 | for tmp_net in stack.nets.values(): |
| 231 | if tmp_net.subnet_name == subnet_name: |
| 232 | stack.routers[router_name].add_subnet(subnet_name) |
| 233 | return |
| 234 | except Exception as e: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 235 | LOG.warning( |
| 236 | 'Could not create RouterInterface: ' + e.__repr__()) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 237 | self.bufferResource.append(resource) |
| 238 | return |
| 239 | |
| 240 | if 'OS::Neutron::FloatingIP' in resource['type']: |
| 241 | try: |
| 242 | port_name = resource['properties']['port_id']['get_resource'] |
| 243 | floating_network_id = resource['properties']['floating_network_id'] |
| 244 | if port_name not in stack.ports: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 245 | stack.ports[port_name] = self.compute.create_port( |
| 246 | port_name, stack_update) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 247 | |
| 248 | stack.ports[port_name].floating_ip = floating_network_id |
| 249 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 250 | LOG.warning('Could not create FloatingIP: ' + str(e)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 251 | return |
| 252 | |
| 253 | if 'OS::Neutron::Router' in resource['type']: |
| 254 | try: |
| 255 | name = resource['properties']['name'] |
| 256 | if name not in stack.routers: |
| 257 | stack.routers[name] = Router(name) |
| 258 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 259 | print('Could not create Router: ' + str(e)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 260 | return |
| 261 | |
| peusterm | eb398a2 | 2017-06-19 12:01:13 +0200 | [diff] [blame] | 262 | if 'OS::Heat::ResourceGroup' in resource['type']: |
| 263 | try: |
| 264 | embedded_resource = resource['properties']['resource_def'] |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 265 | LOG.debug("Found resource in resource group: {}".format( |
| 266 | embedded_resource)) |
| peusterm | eb398a2 | 2017-06-19 12:01:13 +0200 | [diff] [blame] | 267 | # recursively parse embedded resource |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 268 | self.handle_resource( |
| 269 | embedded_resource, stack, dc_label, stack_update) |
| peusterm | eb398a2 | 2017-06-19 12:01:13 +0200 | [diff] [blame] | 270 | except Exception as e: |
| Rafael Schellenberg | aa8823c | 2019-12-06 15:03:56 +0100 | [diff] [blame^] | 271 | print('Could not create Router: ' + str(e)) |
| peusterm | eb398a2 | 2017-06-19 12:01:13 +0200 | [diff] [blame] | 272 | return |
| 273 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 274 | LOG.warning( |
| 275 | 'Could not determine resource type: {}'.format(resource['type'])) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 276 | return |
| 277 | |
| 278 | def shorten_server_name(self, server_name, stack): |
| 279 | """ |
| 280 | Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was |
| 281 | used before. |
| 282 | |
| 283 | :param server_name: The original server name. |
| 284 | :type server_name: ``str`` |
| 285 | :param stack: A reference to the used stack. |
| 286 | :type stack: :class:`heat.resources.stack` |
| 287 | :return: A string with max. 12 characters plus iterator string. |
| 288 | :rtype: ``str`` |
| 289 | """ |
| 290 | server_name = self.shorten_name(server_name, 12) |
| 291 | iterator = 0 |
| 292 | while server_name in stack.servers: |
| 293 | server_name = server_name[0:12] + str(iterator) |
| 294 | iterator += 1 |
| 295 | return server_name |
| 296 | |
| 297 | def shorten_name(self, name, max_size): |
| 298 | """ |
| 299 | Shortens the name to max_size characters and replaces all '-' with '_'. |
| 300 | |
| 301 | :param name: The original string. |
| 302 | :type name: ``str`` |
| 303 | :param max_size: The number of allowed characters. |
| 304 | :type max_size: ``int`` |
| 305 | :return: String with at most max_size characters and without '-'. |
| 306 | :rtype: ``str`` |
| 307 | """ |
| 308 | shortened_name = name.split(':', 1)[0] |
| 309 | shortened_name = shortened_name.replace("-", "_") |
| 310 | shortened_name = shortened_name[0:max_size] |
| 311 | return shortened_name |
| 312 | |
| 313 | def check_template_version(self, version_string): |
| 314 | """ |
| 315 | Checks if a version string is equal or later than 30-04-2015 |
| 316 | |
| 317 | :param version_string: String with the version. |
| 318 | :type version_string: ``str`` |
| 319 | :return: * *True*: if the version is equal or later 30-04-2015. |
| 320 | * *False*: else |
| 321 | :rtype: ``bool`` |
| 322 | """ |
| 323 | r = re.compile('\d{4}-\d{2}-\d{2}') |
| 324 | if not r.match(version_string): |
| 325 | return False |
| 326 | |
| 327 | year, month, day = map(int, version_string.split('-', 2)) |
| 328 | if year < 2015: |
| 329 | return False |
| 330 | if year == 2015: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 331 | if month < 0o4: |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 332 | return False |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 333 | if month == 0o4 and day < 30: |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 334 | return False |
| 335 | return True |