Manually added OpenStack API code
[osm/vim-emu.git] / src / emuvim / api / openstack / heat_parser.py
diff --git a/src/emuvim/api/openstack/heat_parser.py b/src/emuvim/api/openstack/heat_parser.py
new file mode 100644 (file)
index 0000000..a926c10
--- /dev/null
@@ -0,0 +1,274 @@
+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