--- /dev/null
+from __future__ import print_function # TODO remove when print is no longer needed for debugging
+from resources import *
+from datetime import datetime
+import re
+import sys
+import uuid
+import logging
+import ip_handler as IP
+
+
+class HeatParser:
+ """
+ The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu.
+ """
+
+ def __init__(self, compute):
+ self.description = None
+ self.parameter_groups = None
+ self.parameters = None
+ self.resources = None
+ self.outputs = None
+ self.compute = compute
+ self.bufferResource = list()
+
+ def parse_input(self, input_dict, stack, dc_label, stack_update=False):
+ """
+ It will parse the input dictionary into the corresponding classes, which are then stored within the stack.
+
+ :param input_dict: Dictionary with the template version and resources.
+ :type input_dict: ``dict``
+ :param stack: Reference of the stack that should finally contain all created classes.
+ :type stack: :class:`heat.resources.stack`
+ :param dc_label: String that contains the label of the used data center.
+ :type dc_label: ``str``
+ :param stack_update: Specifies if a new stack will be created or a older one will be updated
+ :type stack_update: ``bool``
+ :return: * *True*: If the template version is supported and all resources could be created.
+ * *False*: Else
+ :rtype: ``bool``
+ """
+ if not self.check_template_version(str(input_dict['heat_template_version'])):
+ print('Unsupported template version: ' + input_dict['heat_template_version'], file=sys.stderr)
+ return False
+
+ self.description = input_dict.get('description', None)
+ self.parameter_groups = input_dict.get('parameter_groups', None)
+ self.parameters = input_dict.get('parameters', None)
+ self.resources = input_dict.get('resources', None)
+ self.outputs = input_dict.get('outputs', None)
+ # clear bufferResources
+ self.bufferResource = list()
+
+ for resource in self.resources.values():
+ self.handle_resource(resource, stack, dc_label, stack_update=stack_update)
+
+ # This loop tries to create all classes which had unresolved dependencies.
+ unresolved_resources_last_round = len(self.bufferResource) + 1
+ while len(self.bufferResource) > 0 and unresolved_resources_last_round > len(self.bufferResource):
+ unresolved_resources_last_round = len(self.bufferResource)
+ number_of_items = len(self.bufferResource)
+ while number_of_items > 0:
+ self.handle_resource(self.bufferResource.pop(0), stack, dc_label, stack_update=stack_update)
+ number_of_items -= 1
+
+ if len(self.bufferResource) > 0:
+ print(str(len(self.bufferResource)) +
+ ' classes could not be created, because the dependencies could not be found.')
+ return False
+ return True
+
+ def handle_resource(self, resource, stack, dc_label, stack_update=False):
+ """
+ This function will take a resource (from a heat template) and determines which type it is and creates
+ the corresponding class, with its required parameters, for further calculations (like deploying the stack).
+ If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource
+ within the 'self.bufferResource' list.
+
+ :param resource: Dict which contains all important informations about the type and parameters.
+ :type resource: ``dict``
+ :param stack: Reference of the stack that should finally contain the created class.
+ :type stack: :class:`heat.resources.stack`
+ :param dc_label: String that contains the label of the used data center
+ :type dc_label: ``str``
+ :param stack_update: Specifies if a new stack will be created or a older one will be updated
+ :type stack_update: ``bool``
+ :return: void
+ :rtype: ``None``
+ """
+ if "OS::Neutron::Net" in resource['type']:
+ try:
+ net_name = resource['properties']['name']
+ if net_name not in stack.nets:
+ stack.nets[net_name] = self.compute.create_network(net_name, True)
+
+ except Exception as e:
+ logging.warning('Could not create Net: ' + e.message)
+ return
+
+ if 'OS::Neutron::Subnet' in resource['type'] and "Net" not in resource['type']:
+ try:
+ net_name = resource['properties']['network']['get_resource']
+ if net_name not in stack.nets:
+ net = self.compute.create_network(net_name, stack_update)
+ stack.nets[net_name] = net
+ else:
+ net = stack.nets[net_name]
+
+ net.subnet_name = resource['properties']['name']
+ if 'gateway_ip' in resource['properties']:
+ net.gateway_ip = resource['properties']['gateway_ip']
+ net.subnet_id = resource['properties'].get('id', str(uuid.uuid4()))
+ net.subnet_creation_time = str(datetime.now())
+ if not stack_update:
+ net.set_cidr(IP.get_new_cidr(net.subnet_id))
+ except Exception as e:
+ logging.warning('Could not create Subnet: ' + e.message)
+ return
+
+ if 'OS::Neutron::Port' in resource['type']:
+ try:
+ port_name = resource['properties']['name']
+ if port_name not in stack.ports:
+ port = self.compute.create_port(port_name, stack_update)
+ stack.ports[port_name] = port
+ else:
+ port = stack.ports[port_name]
+
+ if resource['properties']['network']['get_resource'] in stack.nets:
+ net = stack.nets[resource['properties']['network']['get_resource']]
+ if net.subnet_id is not None:
+ port.net_name = net.name
+ port.ip_address = net.get_new_ip_address(port.name)
+ return
+ except Exception as e:
+ logging.warning('Could not create Port: ' + e.message)
+ self.bufferResource.append(resource)
+ return
+
+ if 'OS::Nova::Server' in resource['type']:
+ try:
+ compute_name = str(dc_label) + '_' + str(stack.stack_name) + '_' + str(resource['properties']['name'])
+ shortened_name = str(dc_label) + '_' + str(stack.stack_name) + '_' + \
+ self.shorten_server_name(str(resource['properties']['name']), stack)
+ nw_list = resource['properties']['networks']
+
+ if shortened_name not in stack.servers:
+ server = self.compute.create_server(shortened_name, stack_update)
+ stack.servers[shortened_name] = server
+ else:
+ server = stack.servers[shortened_name]
+
+ server.full_name = compute_name
+ server.template_name = str(resource['properties']['name'])
+ server.command = resource['properties'].get('command', '/bin/sh')
+ server.image = resource['properties']['image']
+ server.flavor = resource['properties']['flavor']
+
+ for port in nw_list:
+ port_name = port['port']['get_resource']
+ # just create a port
+ # we don't know which network it belongs to yet, but the resource will appear later in a valid
+ # template
+ if port_name not in stack.ports:
+ stack.ports[port_name] = self.compute.create_port(port_name, stack_update)
+ server.port_names.append(port_name)
+ return
+ except Exception as e:
+ logging.warning('Could not create Server: ' + e.message)
+ return
+
+ if 'OS::Neutron::RouterInterface' in resource['type']:
+ try:
+ router_name = None
+ subnet_name = resource['properties']['subnet']['get_resource']
+
+ if 'get_resource' in resource['properties']['router']:
+ router_name = resource['properties']['router']['get_resource']
+ else:
+ router_name = resource['properties']['router']
+
+ if router_name not in stack.routers:
+ stack.routers[router_name] = Router(router_name)
+
+ for tmp_net in stack.nets.values():
+ if tmp_net.subnet_name == subnet_name:
+ stack.routers[router_name].add_subnet(subnet_name)
+ return
+ except Exception as e:
+ logging.warning('Could not create RouterInterface: ' + e.__repr__())
+ self.bufferResource.append(resource)
+ return
+
+ if 'OS::Neutron::FloatingIP' in resource['type']:
+ try:
+ port_name = resource['properties']['port_id']['get_resource']
+ floating_network_id = resource['properties']['floating_network_id']
+ if port_name not in stack.ports:
+ stack.ports[port_name] = self.compute.create_port(port_name, stack_update)
+
+ stack.ports[port_name].floating_ip = floating_network_id
+ except Exception as e:
+ logging.warning('Could not create FloatingIP: ' + e.message)
+ return
+
+ if 'OS::Neutron::Router' in resource['type']:
+ try:
+ name = resource['properties']['name']
+ if name not in stack.routers:
+ stack.routers[name] = Router(name)
+ except Exception as e:
+ print('Could not create Router: ' + e.message)
+ return
+
+ logging.warning('Could not determine resource type!')
+ return
+
+ def shorten_server_name(self, server_name, stack):
+ """
+ Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was
+ used before.
+
+ :param server_name: The original server name.
+ :type server_name: ``str``
+ :param stack: A reference to the used stack.
+ :type stack: :class:`heat.resources.stack`
+ :return: A string with max. 12 characters plus iterator string.
+ :rtype: ``str``
+ """
+ server_name = self.shorten_name(server_name, 12)
+ iterator = 0
+ while server_name in stack.servers:
+ server_name = server_name[0:12] + str(iterator)
+ iterator += 1
+ return server_name
+
+ def shorten_name(self, name, max_size):
+ """
+ Shortens the name to max_size characters and replaces all '-' with '_'.
+
+ :param name: The original string.
+ :type name: ``str``
+ :param max_size: The number of allowed characters.
+ :type max_size: ``int``
+ :return: String with at most max_size characters and without '-'.
+ :rtype: ``str``
+ """
+ shortened_name = name.split(':', 1)[0]
+ shortened_name = shortened_name.replace("-", "_")
+ shortened_name = shortened_name[0:max_size]
+ return shortened_name
+
+ def check_template_version(self, version_string):
+ """
+ Checks if a version string is equal or later than 30-04-2015
+
+ :param version_string: String with the version.
+ :type version_string: ``str``
+ :return: * *True*: if the version is equal or later 30-04-2015.
+ * *False*: else
+ :rtype: ``bool``
+ """
+ r = re.compile('\d{4}-\d{2}-\d{2}')
+ if not r.match(version_string):
+ return False
+
+ year, month, day = map(int, version_string.split('-', 2))
+ if year < 2015:
+ return False
+ if year == 2015:
+ if month < 04:
+ return False
+ if month == 04 and day < 30:
+ return False
+ return True