blob: a6416474e44a9a0c5468662e34e3c3fb79fad2a5 [file] [log] [blame]
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
LOG = logging.getLogger("api.openstack.heat.parser")
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 of the HOT could not be created, because the dependencies could not be found.')
print("the problem classes are:")
for br in self.bufferResource:
print("class: %s" % str(br))
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:
LOG.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:
LOG.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 str(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:
LOG.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:
LOG.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:
LOG.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:
LOG.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
if 'OS::Heat::ResourceGroup' in resource['type']:
try:
embedded_resource = resource['properties']['resource_def']
LOG.debug("Found resource in resource group: {}".format(embedded_resource))
# recursively parse embedded resource
self.handle_resource(embedded_resource, stack, dc_label, stack_update)
except Exception as e:
print('Could not create Router: ' + e.message)
return
LOG.warning('Could not determine resource type: {}'.format(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