diff options
Diffstat (limited to 'yardstick/benchmark/contexts/heat.py')
-rw-r--r-- | yardstick/benchmark/contexts/heat.py | 252 |
1 files changed, 194 insertions, 58 deletions
diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index 4ba543b9e..917aa9c39 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -7,29 +7,30 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import absolute_import -from __future__ import print_function - import collections import logging import os -import uuid import errno from collections import OrderedDict import ipaddress import pkg_resources +from yardstick.benchmark import contexts from yardstick.benchmark.contexts.base import Context from yardstick.benchmark.contexts.model import Network from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup from yardstick.benchmark.contexts.model import Server from yardstick.benchmark.contexts.model import update_scheduler_hints -from yardstick.common.openstack_utils import get_neutron_client -from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid +from yardstick.common import exceptions as y_exc +from yardstick.common.openstack_utils import get_shade_client +from yardstick.orchestrator.heat import HeatStack +from yardstick.orchestrator.heat import HeatTemplate from yardstick.common import constants as consts +from yardstick.common import utils from yardstick.common.utils import source_env from yardstick.ssh import SSH +from yardstick.common import openstack_utils LOG = logging.getLogger(__name__) @@ -47,10 +48,9 @@ def h_join(*args): class HeatContext(Context): """Class that represents a context in the logical model""" - __context_type__ = "Heat" + __context_type__ = contexts.CONTEXT_HEAT def __init__(self): - self.name = None self.stack = None self.networks = OrderedDict() self.heat_timeout = None @@ -59,6 +59,7 @@ class HeatContext(Context): self.server_groups = [] self.keypair_name = None self.secgroup_name = None + self.security_group = None self._server_map = {} self.attrs = {} self._image = None @@ -67,14 +68,16 @@ class HeatContext(Context): self._user = None self.template_file = None self.heat_parameters = None - self.neutron_client = None - # generate an uuid to identify yardstick_key - # the first 8 digits of the uuid will be used - self.key_uuid = uuid.uuid4() + self.shade_client = None self.heat_timeout = None - self.key_filename = ''.join( - [consts.YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-', - get_short_key_uuid(self.key_uuid)]) + self.key_filename = None + self.yardstick_gen_key_file = True + self.shade_client = None + self.operator_client = None + self.nodes = [] + self.controllers = [] + self.computes = [] + self.baremetals = [] super(HeatContext, self).__init__() @staticmethod @@ -95,26 +98,45 @@ class HeatContext(Context): return sorted_networks def init(self, attrs): - self.check_environment() - """initializes itself from the supplied arguments""" - self.name = attrs["name"] + """Initializes itself from the supplied arguments""" + super(HeatContext, self).init(attrs) + self.check_environment() self._user = attrs.get("user") self.template_file = attrs.get("heat_template") + + # try looking for external private key when using external heat template + if self.template_file is not None: + self.key_filename = attrs.get("key_filename", None) + if self.key_filename is not None: + # Disable key file generation if an external private key + # has been provided + self.yardstick_gen_key_file = False + + self.shade_client = openstack_utils.get_shade_client() + self.operator_client = openstack_utils.get_shade_operator_client() + + try: + self.read_pod_file(attrs) + except IOError: + LOG.warning("No pod file specified. NVFi metrics will be disabled") + + self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT) if self.template_file: self.heat_parameters = attrs.get("heat_parameters") return self.keypair_name = h_join(self.name, "key") + self.secgroup_name = h_join(self.name, "secgroup") + self.security_group = attrs.get("security_group") + self._image = attrs.get("image") self._flavor = attrs.get("flavor") - self.heat_timeout = attrs.get("timeout", DEFAULT_HEAT_TIMEOUT) - self.placement_groups = [PlacementGroup(name, self, pg_attrs["policy"]) for name, pg_attrs in attrs.get( "placement_groups", {}).items()] @@ -137,7 +159,6 @@ class HeatContext(Context): self._server_map[server.dn] = server self.attrs = attrs - SSH.gen_keys(self.key_filename) def check_environment(self): try: @@ -176,10 +197,13 @@ class HeatContext(Context): template.add_flavor(**self.flavor) self.flavors.add(flavor) - template.add_keypair(self.keypair_name, self.key_uuid) - template.add_security_group(self.secgroup_name) + template.add_keypair(self.keypair_name, self.name) + template.add_security_group(self.secgroup_name, self.security_group) for network in self.networks.values(): + # Using existing network + if network.is_existing(): + continue template.add_network(network.stack_name, network.physical_network, network.provider, @@ -285,38 +309,69 @@ class HeatContext(Context): scheduler_hints) def get_neutron_info(self): - if not self.neutron_client: - self.neutron_client = get_neutron_client() + if not self.shade_client: + self.shade_client = get_shade_client() - networks = self.neutron_client.list_networks() + networks = self.shade_client.list_networks() for network in self.networks.values(): - for neutron_net in networks['networks']: - if neutron_net['name'] == network.stack_name: + for neutron_net in (net for net in networks if net.name == network.stack_name): network.segmentation_id = neutron_net.get('provider:segmentation_id') # we already have physical_network # network.physical_network = neutron_net.get('provider:physical_network') network.network_type = neutron_net.get('provider:network_type') network.neutron_info = neutron_net + def _create_new_stack(self, heat_template): + try: + return heat_template.create(block=True, + timeout=self.heat_timeout) + except KeyboardInterrupt: + raise y_exc.StackCreationInterrupt + except Exception: + LOG.exception("stack failed") + # let the other failures happen, we want stack trace + raise + + def _retrieve_existing_stack(self, stack_name): + stack = HeatStack(stack_name) + if stack.get(): + return stack + else: + LOG.warning("Stack %s does not exist", self.name) + return None + def deploy(self): """deploys template into a stack using cloud""" LOG.info("Deploying context '%s' START", self.name) - heat_template = HeatTemplate(self.name, self.template_file, - self.heat_parameters) + # Check if there was no external private key provided + if self.key_filename is None: + self.key_filename = ''.join( + [consts.YARDSTICK_ROOT_PATH, + 'yardstick/resources/files/yardstick_key-', + self.name]) + # Permissions may have changed since creation; this can be fixed. If we + # overwrite the file, we lose future access to VMs using this key. + # As long as the file exists, even if it is unreadable, keep it intact + if self.yardstick_gen_key_file and not os.path.exists(self.key_filename): + SSH.gen_keys(self.key_filename) + + heat_template = HeatTemplate( + self.name, template_file=self.template_file, + heat_parameters=self.heat_parameters, + os_cloud_config=self._flags.os_cloud_config) if self.template_file is None: self._add_resources_to_template(heat_template) - try: - self.stack = heat_template.create(block=True, - timeout=self.heat_timeout) - except KeyboardInterrupt: - raise SystemExit("\nStack create interrupted") - except: - LOG.exception("stack failed") - # let the other failures happen, we want stack trace - raise + if self._flags.no_setup: + # Try to get an existing stack, returns a stack or None + self.stack = self._retrieve_existing_stack(self.name) + if not self.stack: + self.stack = self._create_new_stack(heat_template) + + else: + self.stack = self._create_new_stack(heat_template) # TODO: use Neutron to get segmentation-id self.get_neutron_info() @@ -332,18 +387,35 @@ class HeatContext(Context): LOG.info("Deploying context '%s' DONE", self.name) + @staticmethod + def _port_net_is_existing(port_info): + net_flags = port_info.get('net_flags', {}) + return net_flags.get(consts.IS_EXISTING) + + @staticmethod + def _port_net_is_public(port_info): + net_flags = port_info.get('net_flags', {}) + return net_flags.get(consts.IS_PUBLIC) + def add_server_port(self, server): - # use private ip from first port in first network - try: - private_port = next(iter(server.ports.values()))[0] - except IndexError: - LOG.exception("Unable to find first private port in %s", server.ports) - raise - server.private_ip = self.stack.outputs[private_port["stack_name"]] + server_ports = server.ports.values() + for server_port in server_ports: + port_info = server_port[0] + port_ip = self.stack.outputs[port_info["stack_name"]] + port_net_is_existing = self._port_net_is_existing(port_info) + port_net_is_public = self._port_net_is_public(port_info) + if port_net_is_existing and (port_net_is_public or + len(server_ports) == 1): + server.public_ip = port_ip + if not server.private_ip or len(server_ports) == 1: + server.private_ip = port_ip + server.interfaces = {} for network_name, ports in server.ports.items(): for port in ports: # port['port'] is either port name from mapping or default network_name + if self._port_net_is_existing(port): + continue server.interfaces[port['port']] = self.make_interface_dict(network_name, port['port'], port['stack_name'], @@ -380,20 +452,29 @@ class HeatContext(Context): "local_ip": private_ip, } + def _delete_key_file(self): + # Only remove the key file if it has been generated by yardstick + if self.yardstick_gen_key_file: + try: + utils.remove_file(self.key_filename) + utils.remove_file(self.key_filename + ".pub") + except OSError: + LOG.exception("There was an error removing the key file %s", + self.key_filename) + def undeploy(self): """undeploys stack from cloud""" + if self._flags.no_teardown: + LOG.info("Undeploying context '%s' SKIP", self.name) + return + if self.stack: LOG.info("Undeploying context '%s' START", self.name) self.stack.delete() self.stack = None LOG.info("Undeploying context '%s' DONE", self.name) - if os.path.exists(self.key_filename): - try: - os.remove(self.key_filename) - os.remove(self.key_filename + ".pub") - except OSError: - LOG.exception("Key filename %s", self.key_filename) + self._delete_key_file() super(HeatContext, self).undeploy() @@ -417,7 +498,7 @@ class HeatContext(Context): with attribute name mapping when using external heat templates """ if isinstance(attr_name, collections.Mapping): - node_name, cname = self.split_name(attr_name['name']) + node_name, cname = self.split_host_name(attr_name['name']) if cname is None or cname != self.name: return None @@ -428,18 +509,46 @@ class HeatContext(Context): server.private_ip = self.stack.outputs.get( attr_name.get("private_ip_attr", object()), None) + + # Try to find interfaces + for key, value in attr_name.get("interfaces", {}).items(): + value["local_ip"] = server.private_ip + for k in ["local_mac", "netmask", "gateway_ip"]: + # Keep explicit None or missing entry as is + value[k] = self.stack.outputs.get(value[k]) + server.interfaces.update({key: value}) else: - server = self._server_map.get(attr_name, None) + try: + server = self._server_map[attr_name] + except KeyError: + attr_name_no_suffix = attr_name.split("-")[0] + server = self._server_map.get(attr_name_no_suffix, None) if server is None: return None - pkey = pkg_resources.resource_string( - 'yardstick.resources', - h_join('files/yardstick_key', get_short_key_uuid(self.key_uuid))).decode('utf-8') - + # Get the pkey + if self.yardstick_gen_key_file: + pkey = pkg_resources.resource_string( + 'yardstick.resources', + h_join('files/yardstick_key', self.name)).decode('utf-8') + key_filename = pkg_resources.resource_filename('yardstick.resources', + h_join('files/yardstick_key', self.name)) + else: + # make sure the file exists before attempting to open it + if not os.path.exists(self.key_filename): + LOG.error("The key_filename provided %s does not exist!", + self.key_filename) + else: + try: + pkey = open(self.key_filename, 'r').read().decode('utf-8') + key_filename = self.key_filename + except IOError: + LOG.error("The key_filename provided (%s) is unreadable.", + self.key_filename) result = { "user": server.context.user, "pkey": pkey, + "key_filename": key_filename, "private_ip": server.private_ip, "interfaces": server.interfaces, "routing_table": self.generate_routing_table(server), @@ -476,3 +585,30 @@ class HeatContext(Context): "physical_network": network.physical_network, } return result + + def _get_physical_nodes(self): + return self.nodes + + def _get_physical_node_for_server(self, server_name): + node_name, ctx_name = self.split_host_name(server_name) + if ctx_name is None or self.name != ctx_name: + return None + + matching_nodes = [s for s in self.servers if s.name == node_name] + if len(matching_nodes) == 0: + return None + + server = openstack_utils.get_server(self.shade_client, + name_or_id=server_name) + + if server: + server = server.toDict() + list_hypervisors = self.operator_client.list_hypervisors() + + for hypervisor in list_hypervisors: + if hypervisor.hypervisor_hostname == server['OS-EXT-SRV-ATTR:hypervisor_hostname']: + for node in self.nodes: + if node['ip'] == hypervisor.host_ip: + return "{}.{}".format(node['name'], self._name) + + return None |