X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=yardstick%2Forchestrator%2Fheat.py;h=754482e4f5697ccb12606427e572be4c054ce43d;hb=326ca99184b896d75266737466516846df29b447;hp=beb63b421a23e9f06dd7066816d38564886f299e;hpb=5ad208bd8d0c1798179de226f28f1e1ec03033fa;p=yardstick.git diff --git a/yardstick/orchestrator/heat.py b/yardstick/orchestrator/heat.py index beb63b421..754482e4f 100644 --- a/yardstick/orchestrator/heat.py +++ b/yardstick/orchestrator/heat.py @@ -10,23 +10,21 @@ """Heat template and stack management""" from __future__ import absolute_import -from __future__ import print_function -from six.moves import range - import collections import datetime import getpass import logging - +import pkg_resources import socket +import tempfile import time -import heatclient -import pkg_resources - +from oslo_serialization import jsonutils from oslo_utils import encodeutils +import shade import yardstick.common.openstack_utils as op_utils +from yardstick.common import exceptions from yardstick.common import template_format log = logging.getLogger(__name__) @@ -36,123 +34,82 @@ HEAT_KEY_UUID_LENGTH = 8 PROVIDER_SRIOV = "sriov" +_DEPLOYED_STACKS = {} + def get_short_key_uuid(uuid): return str(uuid)[:HEAT_KEY_UUID_LENGTH] -class HeatObject(object): - """base class for template and stack""" - - def __init__(self): - self._heat_client = None - self.uuid = None - - @property - def heat_client(self): - """returns a heat client instance""" - - 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.Client( - op_utils.get_heat_api_version(), - endpoint=heat_endpoint, session=sess) - - return self._heat_client - - def status(self): - """returns stack state as a string""" - heat_client = self.heat_client - stack = heat_client.stacks.get(self.uuid) - return stack.stack_status - - -class HeatStack(HeatObject): +class HeatStack(object): """Represents a Heat stack (deployed template) """ - stacks = [] def __init__(self, name): - super(HeatStack, self).__init__() - self.uuid = None self.name = name - self.outputs = None - HeatStack.stacks.append(self) + self.outputs = {} + self._cloud = shade.openstack_cloud() + self._stack = None + + def create(self, template, heat_parameters, wait, timeout): + """Creates an OpenStack stack from a template""" + with tempfile.NamedTemporaryFile('wb', delete=False) as template_file: + template_file.write(jsonutils.dump_as_bytes(template)) + template_file.close() + self._stack = self._cloud.create_stack( + self.name, template_file=template_file.name, wait=wait, + timeout=timeout, **heat_parameters) + outputs = self._stack.outputs + self.outputs = {output['output_key']: output['output_value'] for output + in outputs} + if self.uuid: + _DEPLOYED_STACKS[self.uuid] = self._stack @staticmethod 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 + """Check if any stack has been deployed""" + return len(_DEPLOYED_STACKS) > 0 - log.info("Deleting stack '%s', uuid:%s", self.name, self.uuid) - heat = self.heat_client - template = heat.stacks.get(self.uuid) - start_time = time.time() - template.delete() - - for status in iter(self.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) - - 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. - """ + def delete(self, wait=True): + """Deletes a stack in the target cloud""" if self.uuid is None: return - if not block: - self._delete() - return - - for _ in range(retries): - try: - self._delete() - break - except RuntimeError as err: - log.warning(err.args) - time.sleep(2) - - # if still not deleted try once more and let it fail everything - if self.uuid is not None: - self._delete() - - HeatStack.stacks.remove(self) + ret = self._cloud.delete_stack(self.uuid, wait=wait) + _DEPLOYED_STACKS.pop(self.uuid) + self._stack = None + return ret @staticmethod def delete_all(): - for stack in HeatStack.stacks[:]: + """Delete all deployed stacks""" + for stack in _DEPLOYED_STACKS: stack.delete() - def update(self): - """update a stack""" - raise RuntimeError("not implemented") + @property + def status(self): + """Retrieve the current stack status""" + if self._stack: + return self._stack.status + + @property + def uuid(self): + """Retrieve the current stack ID""" + if self._stack: + return self._stack.id -class HeatTemplate(HeatObject): +class HeatTemplate(object): """Describes a Heat template and a method to deploy template to a stack""" - DESCRIPTION_TEMPLATE = """\ + DESCRIPTION_TEMPLATE = """ Stack built by the yardstick framework for %s on host %s %s. All referred generated resources are prefixed with the template -name (i.e. %s).\ +name (i.e. %s). """ + HEAT_WAIT_LOOP_INTERVAL = 2 + HEAT_STATUS_COMPLETE = 'COMPLETE' + def _init_template(self): timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self._template = { @@ -171,9 +128,7 @@ name (i.e. %s).\ self.resources = self._template['resources'] 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_parameters = {} @@ -184,16 +139,13 @@ name (i.e. %s).\ if template_file: with open(template_file) as stream: - print("Parsing external template:", template_file) + log.info('Parsing external template: %s', template_file) template_str = stream.read() self._template = template_format.parse(template_str) self._parameters = heat_parameters else: self._init_template() - # holds results of requested output after deployment - self.outputs = {} - log.debug("template object '%s' created", name) def add_flavor(self, name, vcpus=1, ram=1024, disk=1, ephemeral=0, @@ -202,9 +154,9 @@ name (i.e. %s).\ """add to the template a Flavor description""" if name is None: name = 'auto' - log.debug("adding Nova::Flavor '%s' vcpus '%d' ram '%d' disk '%d' " + - "ephemeral '%d' is_public '%s' rxtx_factor '%d' " + - "swap '%d' extra_specs '%s' ", + log.debug("adding Nova::Flavor '%s' vcpus '%d' ram '%d' disk '%d' " + "ephemeral '%d' is_public '%s' rxtx_factor '%d' " + "swap '%d' extra_specs '%s'", name, vcpus, ram, disk, ephemeral, is_public, rxtx_factor, swap, str(extra_specs)) @@ -265,7 +217,7 @@ name (i.e. %s).\ self.resources[name]['properties']['mountpoint'] = mountpoint def add_network(self, name, physical_network='physnet1', provider=None, - segmentation_id=None, port_security_enabled=None): + segmentation_id=None, port_security_enabled=None, network_type=None): """add to the template a Neutron Net""" log.debug("adding Neutron::Net '%s'", name) if provider is None: @@ -280,12 +232,14 @@ name (i.e. %s).\ 'type': 'OS::Neutron::ProviderNet', 'properties': { 'name': name, - 'network_type': 'vlan', + 'network_type': 'flat' if network_type is None else network_type, 'physical_network': physical_network, }, } if segmentation_id: self.resources[name]['properties']['segmentation_id'] = segmentation_id + if network_type is None: + self.resources[name]['properties']['network_type'] = 'vlan' # if port security is not defined then don't add to template: # some deployments don't have port security plugin installed if port_security_enabled is not None: @@ -316,7 +270,9 @@ name (i.e. %s).\ 'enable_dhcp': enable_dhcp, } } - if gateway_ip is not None: + if gateway_ip == 'null': + self.resources[name]['properties']['gateway_ip'] = None + elif gateway_ip is not None: self.resources[name]['properties']['gateway_ip'] = gateway_ip self._template['outputs'][name] = { @@ -493,7 +449,7 @@ name (i.e. %s).\ 'type': 'OS::Neutron::SecurityGroup', 'properties': { 'name': name, - 'description': "Group allowing icmp and upd/tcp on all ports", + 'description': "Group allowing IPv4 and IPv6 for icmp and upd/tcp on all ports", 'rules': [ {'remote_ip_prefix': '0.0.0.0/0', 'protocol': 'tcp', @@ -504,7 +460,20 @@ name (i.e. %s).\ 'port_range_min': '1', 'port_range_max': '65535'}, {'remote_ip_prefix': '0.0.0.0/0', - 'protocol': 'icmp'} + 'protocol': 'icmp'}, + {'remote_ip_prefix': '::/0', + 'ethertype': 'IPv6', + 'protocol': 'tcp', + 'port_range_min': '1', + 'port_range_max': '65535'}, + {'remote_ip_prefix': '::/0', + 'ethertype': 'IPv6', + 'protocol': 'udp', + 'port_range_min': '1', + 'port_range_max': '65535'}, + {'remote_ip_prefix': '::/0', + 'ethertype': 'IPv6', + 'protocol': 'ipv6-icmp'} ] } } @@ -514,11 +483,10 @@ name (i.e. %s).\ 'value': {'get_resource': name} } - def add_server(self, name, image, flavor, flavors, ports=None, - networks=None, scheduler_hints=None, user=None, - key_name=None, user_data=None, metadata=None, - additional_properties=None): - """add to the template a Nova Server""" + def add_server(self, name, image, flavor, flavors, ports=None, networks=None, + scheduler_hints=None, user=None, key_name=None, user_data=None, metadata=None, + additional_properties=None, availability_zone=None): + """add to the template a Nova Server """ log.debug("adding Nova::Server '%s', image '%s', flavor '%s', " "ports %s", name, image, flavor, ports) @@ -533,6 +501,8 @@ name (i.e. %s).\ 'flavor': {}, 'networks': [] # list of dictionaries } + if availability_zone: + server_properties["availability_zone"] = availability_zone if flavor in flavors: self.resources[name]['depends_on'].append(flavor) @@ -582,57 +552,28 @@ name (i.e. %s).\ 'value': {'get_resource': name} } - HEAT_WAIT_LOOP_INTERVAL = 2 - HEAT_CREATE_COMPLETE_STATUS = u'CREATE_COMPLETE' - def create(self, block=True, timeout=3600): - """ - creates a template in the target cloud using heat - returns a dict with the requested output values from the template + """Creates a stack in the target based on the stored template - :param block: Wait for Heat create to finish - :type block: bool - :param: timeout: timeout in seconds for Heat create, default 3600s - :type timeout: int + :param block: (bool) Wait for Heat create to finish + :param timeout: (int) Timeout in seconds for Heat create, + default 3600s + :return 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) + log.info("Creating stack '%s' START", self.name) - heat_client = self.heat_client start_time = time.time() - stack.uuid = self.uuid = heat_client.stacks.create( - stack_name=self.name, template=self._template, - parameters=self.heat_parameters)['stack']['id'] + stack = HeatStack(self.name) + stack.create(self._template, self.heat_parameters, block, timeout) if not block: - self.outputs = stack.outputs = {} - end_time = time.time() - log.info("Created stack '%s' in %.3e secs", - self.name, end_time - start_time) + log.info("Creating stack '%s' DONE in %d secs", + self.name, time.time() - start_time) return stack - time_limit = start_time + timeout - for status in iter(self.status, self.HEAT_CREATE_COMPLETE_STATUS): - log.debug("stack state %s", status) - if status == u'CREATE_FAILED': - stack_status_reason = heat_client.stacks.get(self.uuid).stack_status_reason - heat_client.stacks.delete(self.uuid) - raise RuntimeError(stack_status_reason) - if time.time() > time_limit: - raise RuntimeError("Heat stack create timeout") - - time.sleep(self.HEAT_WAIT_LOOP_INTERVAL) - - end_time = time.time() - outputs = heat_client.stacks.get(self.uuid).outputs - log.info("Created stack '%s' in %.3e secs", - self.name, end_time - start_time) - - # keep outputs as unicode - self.outputs = {output["output_key"]: output["output_value"] for output - in outputs} + if stack.status != self.HEAT_STATUS_COMPLETE: + raise exceptions.HeatTemplateError(stack_name=self.name) - stack.outputs = self.outputs + log.info("Creating stack '%s' DONE in %d secs", + self.name, time.time() - start_time) return stack