Merge "bottlenecks openstack Newton support"
authorYu Yang (Gabriel) <Gabriel.yuyang@huawei.com>
Wed, 4 Jan 2017 06:36:33 +0000 (06:36 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Wed, 4 Jan 2017 06:36:33 +0000 (06:36 +0000)
utils/infra_setup/heat/__init__.py [changed mode: 0755->0644]
utils/infra_setup/heat/common.py
utils/infra_setup/heat/manager.py [changed mode: 0755->0644]
utils/infra_setup/heat/template.py

old mode 100755 (executable)
new mode 100644 (file)
index 83b8d15..6dbd8d7
@@ -1,5 +1,5 @@
 ##############################################################################
-# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others.
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
 #
 # All rights reserved. This program and the accompanying materials
 # are made available under the terms of the Apache License, Version 2.0
index c4a7824..28257ac 100755 (executable)
 ##############################################################################
 
 import os
-import re
-import ConfigParser
 import logging
-import fileinput
 
-import consts.files as files
-import consts.parameters as parameters
+from keystoneauth1 import loading
+from keystoneauth1 import session
 
-# ------------------------------------------------------
-# List of common variables
-# ------------------------------------------------------
+log = logging.getLogger(__name__)
 
-LOG = None
-CONF_FILE = None
-DEPLOYMENT_UNIT = None
-ITERATIONS = None
+DEFAULT_HEAT_API_VERSION = '1'
+DEFAULT_NOVA_API_VERSION = '2'
+DEFAULT_GLANCE_API_VERSION = '2'
 
-BASE_DIR = None
-TEMPLATE_DIR = None
-TEMPLATE_NAME = None
-TEMPLATE_EXTENSION = None
 
-# ------------------------------------------------------
-# Initialization and Input 'heat_templates/'validation
-# ------------------------------------------------------
+def get_credentials():
+    """Returns a creds dictionary filled with parsed from env"""
+    creds = {}
 
+    keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
 
-def init(api=False):
-    global BASE_DIR
-    # BASE_DIR = os.getcwd()
-    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-    BASE_DIR = BASE_DIR.replace('/heat', '')
-    BASE_DIR = InputValidation.validate_directory_exist_and_format(
-        BASE_DIR, "Error 000001")
-
-    conf_file_init(api)
-    log_init()
-    general_vars_init(api)
-
-
-def conf_file_init(api=False):
-    global CONF_FILE
-    if api:
-        CONF_FILE = ConfigurationFile(files.get_sections_api(),
-                                      '/tmp/bottlenecks.conf')
-    else:
-        CONF_FILE = ConfigurationFile(cf.get_sections(),
-                                      '/tmp/bottlenecks.conf')
-
-
-def general_vars_init(api=False):
-    global TEMPLATE_EXTENSION
-    global TEMPLATE_NAME
-    global TEMPLATE_DIR
-    global ITERATIONS
-
-    TEMPLATE_EXTENSION = '.yaml'
-
-    # Check Section in Configuration File
-    InputValidation.validate_configuration_file_section(
-        files.GENERAL,
-        "Section " + files.GENERAL +
-        "is not present in configuration file")
-
-    InputValidation.validate_configuration_file_section(
-        files.OPENSTACK,
-        "Section " + files.OPENSTACK +
-        "is not present in configuration file")
-
-    TEMPLATE_DIR = '/tmp/heat_templates/'
-
-    if not api:
-        # Validate template name
-        InputValidation.validate_configuration_file_parameter(
-            files.GENERAL,
-            files.TEMPLATE_NAME,
-            "Parameter " + files.TEMPLATE_NAME +
-            "is not present in configuration file")
-        TEMPLATE_NAME = CONF_FILE.get_variable(files.GENERAL,
-                                               files.TEMPLATE_NAME)
-        InputValidation.validate_file_exist(
-            TEMPLATE_DIR + TEMPLATE_NAME,
-            "The provided template file does not exist")
-
-    # Validate and assign Iterations
-    if files.ITERATIONS in CONF_FILE.get_variable_list(files.GENERAL):
-        ITERATIONS = int(CONF_FILE.get_variable(files.GENERAL,
-                                                files.ITERATIONS))
+    if keystone_api_version is None or keystone_api_version == '2':
+        keystone_v3 = False
+        tenant_env = 'OS_TENANT_NAME'
+        tenant = 'tenant_name'
     else:
-        ITERATIONS = 1
-
-
-def log_init():
-    global LOG
-    LOG = logging.getLogger()
-    LOG.setLevel(level=logging.DEBUG)
-    log_formatter = logging.Formatter("%(asctime)s --- %(message)s")
-    file_handler = logging.FileHandler("{0}/{1}.log".format("./", "benchmark"))
-    file_handler.setFormatter(log_formatter)
-    file_handler.setLevel(logging.DEBUG)
-    LOG.addHandler(file_handler)
-
-# ------------------------------------------------------
-# Configuration file access
-# ------------------------------------------------------
-
-
-class ConfigurationFile:
-    """
-    Used to extract data from the configuration file
-    """
-
-    def __init__(self, sections, config_file='conf.cfg'):
-        """
-        Reads configuration file sections
-
-        :param sections: list of strings representing the sections to be
-                         loaded
-        :param config_file: name of the configuration file (string)
-        :return: None
-        """
-        InputValidation.validate_string(
-            config_file, "The configuration file name must be a string")
-        InputValidation.validate_file_exist(
-            config_file, 'The provided configuration file does not exist')
-        self.config = ConfigParser.ConfigParser()
-        self.config.read(config_file)
-        for section in sections:
-            setattr(
-                self, section, ConfigurationFile.
-                _config_section_map(section, self.config))
-
-    @staticmethod
-    def _config_section_map(section, config_file):
-        """
-        Returns a dictionary with the configuration values for the specific
-        section
-
-        :param section: section to be loaded (string)
-        :param config_file: name of the configuration file (string)
-        :return: dict
-        """
-        dict1 = dict()
-        options = config_file.options(section)
-        for option in options:
-            dict1[option] = config_file.get(section, option)
-        return dict1
-
-    def get_variable(self, section, variable_name):
-        """
-        Returns the value correspondent to a variable
-
-        :param section: section to be loaded (string)
-        :param variable_name: name of the variable (string)
-        :return: string
-        """
-        message = "The variable name must be a string"
-        InputValidation.validate_string(variable_name, message)
-        if variable_name in self.get_variable_list(section):
-            sect = getattr(self, section)
-            return sect[variable_name]
-        else:
-            exc_msg = 'Parameter {} is not in the {} section of the ' \
-                      'conf file'.format(variable_name, section)
-            raise ValueError(exc_msg)
-
-    def get_variable_list(self, section):
-        """
-        Returns the list of the available variables in a section
-        :param section: section to be loaded (string)
-        :return: list
-        """
-        try:
-            return getattr(self, section)
-        except:
-            msg = 'Section {}  not found in the configuration file'.\
-                format(section)
-            raise ValueError(msg)
-
-# ------------------------------------------------------
-# Manage files
-# ------------------------------------------------------
-
-
-def get_heat_template_params():
-    """
-    Returns the list of deployment parameters from the configuration file
-    for the heat template
-
-    :return: dict
-    """
-    heat_parameters_list = CONF_FILE.get_variable_list(
-        files.DEPLOYMENT_PARAMETERS)
-    testcase_parameters = dict()
-    for param in heat_parameters_list:
-        testcase_parameters[param] = CONF_FILE.get_variable(
-            files.DEPLOYMENT_PARAMETERS, param)
-    return testcase_parameters
-
-
-def get_testcase_params():
-    """
-    Returns the list of testcase parameters from the configuration file
-
-    :return: dict
-    """
-    testcase_parameters = dict()
-    parameters = CONF_FILE.get_variable_list(files.TESTCASE_PARAMETERS)
-    for param in parameters:
-        testcase_parameters[param] = CONF_FILE.get_variable(
-            files.TESTCASE_PARAMETERS, param)
-    return testcase_parameters
-
-
-def get_file_first_line(file_name):
-    """
-    Returns the first line of a file
-
-    :param file_name: name of the file to be read (str)
-    :return: str
-    """
-    message = "name of the file must be a string"
-    InputValidation.validate_string(file_name, message)
-    message = 'file {} does not exist'.format(file_name)
-    InputValidation.validate_file_exist(file_name, message)
-    res = open(file_name, 'r')
-    return res.readline()
-
-
-def replace_in_file(file, text_to_search, text_to_replace):
-    """
-    Replaces a string within a file
-
-    :param file: name of the file (str)
-    :param text_to_search: text to be replaced
-    :param text_to_replace: new text that will replace the previous
-    :return: None
-    """
-    message = 'text to be replaced in the file must be a string'
-    InputValidation.validate_string(text_to_search, message)
-    message = 'text to replace in the file must be a string'
-    InputValidation.validate_string(text_to_replace, message)
-    message = "name of the file must be a string"
-    InputValidation.validate_string(file, message)
-    message = "The file does not exist"
-    InputValidation.validate_file_exist(file, message)
-    for line in fileinput.input(file, inplace=True):
-        print(line.replace(text_to_search, text_to_replace).rstrip())
-
-# ------------------------------------------------------
-# Shell interaction
-# ------------------------------------------------------
-
-
-def run_command(command):
-    LOG.info("Running command: {}".format(command))
-    return os.system(command)
-
-# ------------------------------------------------------
-# Expose variables to other modules
-# ------------------------------------------------------
-
-
-def get_base_dir():
-    return BASE_DIR
-
-
-def get_template_dir():
-    return TEMPLATE_DIR
-
-# ------------------------------------------------------
-# Configuration Variables from Config File
-# ------------------------------------------------------
-
-
-def get_deployment_configuration_variables_from_conf_file():
-    variables = dict()
-    types = dict()
-    all_variables = CONF_FILE.get_variable_list(files.EXPERIMENT_VNF)
-    for var in all_variables:
-        v = CONF_FILE.get_variable(files.EXPERIMENT_VNF, var)
-        type = re.findall(r'@\w*', v)
-        values = re.findall(r'\"(.+?)\"', v)
-        variables[var] = values
-        try:
-            types[var] = type[0][1:]
-        except IndexError:
-            LOG.debug("No type has been specified for variable " + var)
-    return variables
-
-# ------------------------------------------------------
-# benchmarks from Config File
-# ------------------------------------------------------
-
-
-def get_benchmarks_from_conf_file():
-    requested_benchmarks = list()
-    benchmarks = CONF_FILE.get_variable(
-        files.GENERAL, files.BENCHMARKS).split(', ')
-    for benchmark in benchmarks:
-        requested_benchmarks.append(benchmark)
-    return requested_benchmarks
-
-
-class InputValidation(object):
-
-    @staticmethod
-    def validate_string(param, message):
-        if not isinstance(param, str):
-            raise ValueError(message)
-        return True
-
-    @staticmethod
-    def validate_integer(param, message):
-        if not isinstance(param, int):
-            raise ValueError(message)
-        return True
-
-    @staticmethod
-    def validate_dictionary(param, message):
-        if not isinstance(param, dict):
-            raise ValueError(message)
-        return True
-
-    @staticmethod
-    def validate_file_exist(file_name, message):
-        if not os.path.isfile(file_name):
-            raise ValueError(message + ' ' + file_name)
-        return True
-
-    @staticmethod
-    def validate_directory_exist_and_format(directory, message):
-        if not os.path.isdir(directory):
-            raise ValueError(message)
-        if not directory.endswith('/'):
-            return directory + '/'
-        return directory
-
-    @staticmethod
-    def validate_configuration_file_parameter(section, parameter, message):
-        params = CONF_FILE.get_variable_list(section)
-        if parameter not in params:
-            raise ValueError(message)
-        return True
-
-    @staticmethod
-    def validate_configuration_file_section(section, message):
-        if section not in files.get_sections():
-            raise ValueError(message)
-        return True
-
-    @staticmethod
-    def validate_boolean(boolean, message):
-        if isinstance(boolean, bool):
-            return boolean
-        if isinstance(boolean, str):
-            if boolean == 'True':
-                return True
-            if boolean == 'False':
-                return False
-        raise ValueError(message)
-
-    @staticmethod
-    def validate_os_credentials(credentials):
-        if not isinstance(credentials, dict):
-            raise ValueError(
-                'The provided openstack_credentials '
-                'variable must be in dictionary format')
-
-        credential_keys = ['user', 'password', 'ip_controller', 'heat_url',
-                           'auth_uri', 'project']
-        missing = [
-            credential_key
-            for credential_key in credential_keys
-            if credential_key not in credentials.keys()
-        ]
-        if len(missing) == 0:
-            return True
-        msg = 'OpenStack Credentials Error! ' \
-              'The following parameters are missing: {}'.\
-            format(", ".join(missing))
-        raise ValueError(msg)
+        keystone_v3 = True
+        tenant_env = 'OS_PROJECT_NAME'
+        tenant = 'project_name'
+
+    # The most common way to pass these info to the script is to do it
+    # through environment variables.
+    creds.update({
+        "username": os.environ.get("OS_USERNAME"),
+        "password": os.environ.get("OS_PASSWORD"),
+        "auth_url": os.environ.get("OS_AUTH_URL"),
+        tenant: os.environ.get(tenant_env)
+    })
+
+    if keystone_v3:
+        if os.getenv('OS_USER_DOMAIN_NAME') is not None:
+            creds.update({
+                "user_domain_name": os.getenv('OS_USER_DOMAIN_NAME')
+            })
+        if os.getenv('OS_PROJECT_DOMAIN_NAME') is not None:
+            creds.update({
+                "project_domain_name": os.getenv('OS_PROJECT_DOMAIN_NAME')
+            })
+
+    cacert = os.environ.get("OS_CACERT")
+
+    if cacert is not None:
+        # each openstack client uses differnt kwargs for this
+        creds.update({"cacert": cacert,
+                      "ca_cert": cacert,
+                      "https_ca_cert": cacert,
+                      "https_cacert": cacert,
+                      "ca_file": cacert})
+        creds.update({"insecure": "True", "https_insecure": "True"})
+        if not os.path.isfile(cacert):
+            log.info("WARNING: The 'OS_CACERT' environment variable is set\
+                      to %s but the file does not exist." % cacert)
+
+    return creds
+
+
+def get_session_auth():
+    loader = loading.get_plugin_loader('password')
+    creds = get_credentials()
+    auth = loader.load_from_options(**creds)
+    return auth
+
+
+def get_session():
+    auth = get_session_auth()
+    return session.Session(auth=auth)
+
+
+def get_endpoint(service_type, endpoint_type='publicURL'):
+    auth = get_session_auth()
+    return get_session().get_endpoint(auth=auth,
+                                      service_type=service_type,
+                                      endpoint_type=endpoint_type)
+
+
+def get_heat_api_version():
+    api_version = os.getenv('HEAT_API_VERSION')
+    if api_version is not None:
+        log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
+        return api_version
+    return DEFAULT_HEAT_API_VERSION
+
+def get_nova_api_version():
+    api_version = os.getenv('OS_COMPUTE_API_VERSION')
+    if api_version is not None:
+        log.info("NOVA_API_VERSION is set in env as '%s'", api_version)
+        return api_version
+    return DEFAULT_NOVA_API_VERSION
+
+
+def get_glance_api_version():
+    api_version = os.getenv('OS_IMAGE_API_VERSION')
+    if api_version is not None:
+        log.info("GLANCE_API_VERSION is set in env as '%s'", api_version)
+        return api_version
+    return DEFAULT_GLANCE_API_VERSION
\ No newline at end of file
old mode 100755 (executable)
new mode 100644 (file)
index f5a9b88..3a270ac
@@ -7,82 +7,68 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
-from heatclient import client as heat_client
-from keystoneclient.v2_0 import client as keystone_client
-from heatclient.common import template_utils
-
-import heat.common as common
-
-
-class HeatManager:
-
-    def __init__(self, credentials):
-        self.user = credentials['user']
-        self.password = credentials['password']
-        self.controller_ip = credentials['controller_ip']
-        self.heat_url = credentials['heat_url']
-        self.auth_uri = credentials['auth_uri']
-        self.project_id = credentials['project']
-        self.heat = None
-
-    def heat_init(self):
-        keystone = keystone_client.Client(username=self.user,
-                                          password=self.password,
-                                          tenant_name=self.project_id,
-                                          auth_url=self.auth_uri)
-        auth_token = keystone.auth_token
-        self.heat_url = keystone.service_catalog.url_for(
-            service_type='orchestration')
-        self.heat = heat_client.Client('1', endpoint=self.heat_url,
-                                       token=auth_token)
-
-    def stacks_list(self, name=None):
-        for stack in self.heat.stacks.list():
-            if (name and stack.stack_name == name) or not name:
-                common.LOG.info("stack name " + stack.stack_name)
-                common.LOG.info("stack status " + stack.stack_status)
-
-    def stack_generate(self, template_file, stack_name, parameters):
-        self.heat_init()
-        self.stacks_list()
-        tpl_files, template = template_utils.get_template_contents(
-            template_file)
-
-        fields = {
-            'template': template,
-            'files': dict(list(tpl_files.items()))
-        }
-        self.heat.stacks.create(stack_name=stack_name, files=fields['files'],
-                                template=template, parameters=parameters)
-        self.stacks_list(stack_name)
-
-    def stack_is_deployed(self, stack_name):
-        self.heat_init()
-        if stack_name in self.heat.stacks.list():
-            return True
-        return False
+import time
+import common as op_utils
+from glanceclient.client import Client as GlanceClient
+from novaclient.client import Client as NovaClient
+
+
+def _get_glance_client():
+    sess = op_utils.get_session()
+    return GlanceClient(
+        op_utils.get_glance_api_version(),
+        session=sess)
+
+
+def _get_nova_client():
+    sess = op_utils.get_session()
+
+    return NovaClient(
+        op_utils.get_nova_api_version(),
+        session=sess)
+
+
+def stack_create_images(
+        imagefile=None,
+        image_name="bottlenecks_image"):
+    print "========== Create image in OS =========="
 
-    def stack_check_status(self, stack_name):
-        for stack in self.heat.stacks.list():
-            if stack.stack_name == stack_name:
-                return stack.stack_status
-        return 'NOT_FOUND'
-
-    def heat_validate_template(self, heat_template_file):
-        self.heat_init()
-        if not self.heat.stacks.validate(template=open(heat_template_file,
-                                                       'r').read()):
-            raise ValueError('The provided heat template "' +
-                             heat_template_file +
-                             '" is in the wrong format')
-
-    def stack_delete(self, stack_name):
-        self.heat_init()
-        try:
-            for stack in self.heat.stacks.list():
-                if stack.stack_name == stack_name:
-                    self.heat.stacks.delete(stack.id)
-                    return True
-        except:
-            pass
+    if imagefile is None:
+        print "imagefile not set/found"
         return False
+
+    glance = _get_glance_client()
+    image = glance.images.create(
+        name=image_name,
+        disk_format="qcow2",
+        container_format="bare")
+    with open(imagefile) as fimage:
+        glance.images.upload(image.id, fimage)
+
+    timeInQueue = 0
+    img_status = image.status
+    while img_status == "queued" and timeInQueue < 30:
+        print "  image's status: " + img_status
+        time.sleep(1)
+        timeInQueue = timeInQueue + 1
+        img_status = glance.images.get(image.id).status
+
+    print "After %d seconds,image status is [%s]" % (timeInQueue, img_status)
+    return True if img_status == "active" else False
+
+
+def stack_create_keypairs(key_path, name="bottlenecks_keypair"):
+    print "========== Add keypairs in OS =========="
+    nova = _get_nova_client()
+    with open(key_path) as pkey:
+        nova.keypairs.create(name=name, public_key=pkey.read())
+
+
+def stack_create_flavors(
+        name="bottlenecks_flavor",
+        ram=4096,
+        vcpus=2,
+        disk=10):
+    print "========== Create flavors in OS =========="
+    nova = _get_nova_client()
+    nova.flavors.create(name=name, ram=ram, vcpus=vcpus, disk=disk)
index f71e916..28c20b7 100755 (executable)
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
-"""to create heat templates from the base template
-"""
-import os
-import json
-import shutil
-import common
-import consts.parameters as parameters
+"""Heat template and stack management"""
 
+import time
+import sys
+import logging
 
-class TreeNode:
+from heatclient import client as heatclient
+import common as op_utils
 
-    def __init__(self):
-        self.up = None
-        self.down = []
-        self.variable_name = ''
-        self.variable_value = 0
 
-    def add_child(self, node):
-        node.up = self
-        self.down.append(node)
+log = logging.getLogger(__name__)
 
-    def get_parent(self):
-        return self.up
 
-    def get_children(self):
-        if len(self.down) == 0:
-            return []
-        return self.down
+class HeatObject(object):
+    ''' base class for template and stack'''
+    def __init__(self):
+        self._heat_client = None
+        self.uuid = None
 
-    def get_variable_name(self):
-        return self.variable_name
+    def _get_heat_client(self):
+        '''returns a heat client instance'''
 
-    def get_variable_value(self):
-        return self.variable_value
+        if self._heat_client is None:
+            sess = op_utils.get_session()
+            heat_endpoint = op_utils.get_endpoint(service_type='orchestration')
+            self._heat_client = heatclient.Client(
+                op_utils.get_heat_api_version(),
+                endpoint=heat_endpoint, session=sess)
 
-    def set_variable_name(self, name):
-        self.variable_name = name
+        return self._heat_client
 
-    def set_variable_value(self, value):
-        self.variable_value = value
+    def status(self):
+        '''returns stack state as a string'''
+        heat = self._get_heat_client()
+        stack = heat.stacks.get(self.uuid)
+        return getattr(stack, 'stack_status')
 
-    def get_path(self):
-        ret_val = []
-        if not self.up:
-            ret_val.append(self)
-            return ret_val
-        for node in self.up.get_path():
-            ret_val.append(node)
-        ret_val.append(self)
-        return ret_val
 
-    def __str__(self):
-        return str(self.variable_name) + " --> " + str(self.variable_value)
+class HeatStack(HeatObject):
+    ''' Represents a Heat stack (deployed template) '''
+    stacks = []
 
-    def __repr__(self):
-        return str(self.variable_name) + " = " + str(self.variable_value)
+    def __init__(self, name):
+        super(HeatStack, self).__init__()
+        self.uuid = None
+        self.name = name
+        self.outputs = None
+        HeatStack.stacks.append(self)
 
     @staticmethod
-    def _get_leaves(node, leaves):
-        children = node.get_children()
-        if len(children) == 0:
-            leaves.append(node)
+    def stacks_exist():
+        '''check if any stack has been deployed'''
+        return len(HeatStack.stacks) > 0
+
+    def _delete(self):
+        '''deletes a stack from the target cloud using heat'''
+        if self.uuid is None:
             return
-        for child in children:
-            TreeNode._get_leaves(child, leaves)
+
+        log.info("Deleting stack '%s', uuid:%s", self.name, self.uuid)
+        heat = self._get_heat_client()
+        template = heat.stacks.get(self.uuid)
+        start_time = time.time()
+        template.delete()
+        status = self.status()
+
+        while status != u'DELETE_COMPLETE':
+            log.debug("stack state %s", status)
+            if status == u'DELETE_FAILED':
+                raise RuntimeError(
+                    heat.stacks.get(self.uuid).stack_status_reason)
+
+            time.sleep(2)
+            status = self.status()
+
+        end_time = time.time()
+        log.info("Deleted stack '%s' in %d secs", self.name,
+                 end_time - start_time)
+        self.uuid = None
+
+    def delete(self, block=True, retries=3):
+        '''deletes a stack in the target cloud using heat (with retry)
+        Sometimes delete fail with "InternalServerError" and the next attempt
+        succeeds. So it is worthwhile to test a couple of times.
+        '''
+        if self.uuid is None:
+            return
+
+        if not block:
+            try:
+                self._delete()
+            except RuntimeError as err:
+                log.warn(err.args)
+            HeatStack.stacks.remove(self)
+            return
+
+        i = 0
+        while i < retries:
+            try:
+                self._delete()
+                break
+            except RuntimeError as err:
+                log.warn(err.args)
+                time.sleep(2)
+            i += 1
+
+        if self.uuid is not None:
+           sys.exit("delete stack failed!!!")
+        else:
+           HeatStack.stacks.remove(self)
 
     @staticmethod
-    def get_leaves(node):
-        leaves = list()
-        TreeNode._get_leaves(node, leaves)
-        return leaves
-
-template_name = parameters.TEST_TEMPLATE_NAME
-
-
-def generates_templates(base_heat_template, deployment_configuration):
-    # parameters loaded from file
-    template_dir = common.get_template_dir()
-    template_extension = parameters.TEMPLATE_EXTENSION
-    template_base_name = base_heat_template
-
-    variables = deployment_configuration
-
-    # Delete the templates generated in previous running
-    common.LOG.info("Removing the heat templates already generated")
-    command = "rm {}{}_*".format(template_dir, template_name)
-    os.system(command)
-
-    # Creation of the tree with all the new configurations
-    common.LOG.info("Creation of a tree with all new configurations")
-    tree = TreeNode()
-    for variable in variables:
-        leaves = TreeNode.get_leaves(tree)
-        common.LOG.debug("LEAVES: " + str(leaves))
-        common.LOG.debug("VALUES: " + str(variables[variable]))
-
-        for value in variables[variable]:
-            for leaf in leaves:
-                new_node = TreeNode()
-                new_node.set_variable_name(variable)
-                new_node.set_variable_value(value)
-                leaf.add_child(new_node)
-
-    common.LOG.debug("CONFIGURATION TREE: " + str(tree))
-
-    common.LOG.info("Heat Template and metadata file creation")
-    leaves = TreeNode.get_leaves(tree)
-    counter = 1
-    for leaf in leaves:
-        heat_template_vars = leaf.get_path()
-        if os.path.isabs(template_base_name):
-            base_template = template_base_name
+    def delete_all():
+        for stack in HeatStack.stacks[:]:
+            stack.delete()
+
+    def update(self):
+        '''update a stack'''
+        pass
+
+
+class HeatTemplate(HeatObject):
+    '''Describes a Heat template and a method to deploy template to a stack'''
+
+    def __init__(self, name, template_file=None, heat_parameters=None):
+        super(HeatTemplate, self).__init__()
+        self.name = name
+        self.state = "NOT_CREATED"
+        self.keystone_client = None
+        self.heat_client = None
+        self.heat_parameters = {}
+
+        # heat_parameters is passed to heat in stack create, empty dict when
+        # yardstick creates the template (no get_param in resources part)
+        if heat_parameters:
+            self.heat_parameters = heat_parameters
+
+        if template_file:
+            with open(template_file) as template:
+                print "Parsing external template:", template_file
+                template_str = template.read()
+                self._template = template_str
+            self._parameters = heat_parameters
         else:
-            base_template = template_dir + template_base_name
-        new_template = template_dir + template_name
-        new_template += "_" + str(counter) + template_extension
-        shutil.copy(base_template, new_template)
-
-        metadata = dict()
-        for var in heat_template_vars:
-            if var.get_variable_name():
-                common.replace_in_file(new_template, "#" +
-                                       var.get_variable_name(),
-                                       var.get_variable_value())
-                metadata[var.get_variable_name()] = var.get_variable_value()
-
-        # Save the metadata on a JSON file
-        with open(new_template + ".json", 'w') as outfile:
-            json.dump(metadata, outfile)
-
-        common.LOG.debug("Heat Templates and Metadata file " + str(counter) +
-                         " created")
-        counter += 1
-
-    # Creation of the template files
-    common.LOG.info(str(counter - 1) + " Heat Templates and Metadata files "
-                                       "created")
-
-
-def get_all_heat_templates(template_dir, template_extension):
-    template_files = list()
-    for dirname, dirnames, filenames in os.walk(template_dir):
-        for filename in filenames:
-            if template_extension in filename and filename.endswith(
-                    template_extension) and template_name in filename:
-                template_files.append(filename)
-    template_files.sort()
-    return template_files
+            sys.exit("\nno such template file.")
+
+        # holds results of requested output after deployment
+        self.outputs = {}
+
+        log.debug("template object '%s' created", name)
+
+    def create(self, block=True):
+        '''creates a template in the target cloud using heat
+        returns a dict with the requested output values from the template'''
+        log.info("Creating stack '%s'", self.name)
+
+        # create stack early to support cleanup, e.g. ctrl-c while waiting
+        stack = HeatStack(self.name)
+
+        heat = self._get_heat_client()
+        end_time = start_time = time.time()
+        print(self._template)
+        stack.uuid = self.uuid = heat.stacks.create(
+            stack_name=self.name, template=self._template,
+            parameters=self.heat_parameters)['stack']['id']
+
+        status = self.status()
+
+        if block:
+            while status != u'CREATE_COMPLETE':
+                log.debug("stack state %s", status)
+                if status == u'CREATE_FAILED':
+                    raise RuntimeError(getattr(heat.stacks.get(self.uuid),
+                                               'stack_status_reason'))
+
+                time.sleep(2)
+                status = self.status()
+
+            end_time = time.time()
+            outputs = getattr(heat.stacks.get(self.uuid), 'outputs')
+
+        for output in outputs:
+            self.outputs[output["output_key"].encode("ascii")] = \
+                output["output_value"].encode("ascii")
+
+        log.info("Created stack '%s' in %d secs",
+                 self.name, end_time - start_time)
+
+        stack.outputs = self.outputs
+        return stack