diff options
Diffstat (limited to 'yardstick/benchmark')
91 files changed, 3793 insertions, 1828 deletions
diff --git a/yardstick/benchmark/contexts/__init__.py b/yardstick/benchmark/contexts/__init__.py index e69de29bb..d50f08cc3 100644 --- a/yardstick/benchmark/contexts/__init__.py +++ b/yardstick/benchmark/contexts/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2016-2017 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +CONTEXT_DUMMY = "Dummy" +CONTEXT_HEAT = "Heat" +CONTEXT_KUBERNETES = "Kubernetes" +CONTEXT_NODE = "Node" +CONTEXT_STANDALONEOVSDPDK = "StandaloneOvsDpdk" +CONTEXT_STANDALONESRIOV = "StandaloneSriov" diff --git a/yardstick/benchmark/contexts/base.py b/yardstick/benchmark/contexts/base.py index c9b5b51c9..f3f5879eb 100644 --- a/yardstick/benchmark/contexts/base.py +++ b/yardstick/benchmark/contexts/base.py @@ -6,33 +6,108 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import absolute_import + import abc +import errno import six +import os + +from yardstick.common import constants +from yardstick.common import utils +from yardstick.common import yaml_loader +from yardstick.common.constants import YARDSTICK_ROOT_PATH + + +class Flags(object): + """Class to represent the status of the flags in a context""" + + _FLAGS = {'no_setup': False, + 'no_teardown': False, + 'os_cloud_config': constants.OS_CLOUD_DEFAULT_CONFIG} + + def __init__(self, **kwargs): + for name, value in self._FLAGS.items(): + setattr(self, name, value) + + for name, value in ((name, value) for (name, value) in kwargs.items() + if name in self._FLAGS): + setattr(self, name, value) -import yardstick.common.utils as utils + def parse(self, **kwargs): + """Read in values matching the flags stored in this object""" + if not kwargs: + return + + for name, value in ((name, value) for (name, value) in kwargs.items() + if name in self._FLAGS): + setattr(self, name, value) @six.add_metaclass(abc.ABCMeta) class Context(object): """Class that represents a context in the logical model""" list = [] + SHORT_TASK_ID_LEN = 8 - @staticmethod - def split_name(name, sep='.'): - try: - name_iter = iter(name.split(sep)) - except AttributeError: - # name is not a string - return None, None - return next(name_iter), next(name_iter, None) - - def __init__(self): + def __init__(self, host_name_separator='.'): Context.list.append(self) + self._flags = Flags() + self._name = None + self._task_id = None + self.file_path = None + self._host_name_separator = host_name_separator - @abc.abstractmethod def init(self, attrs): - """Initiate context.""" + """Initiate context""" + self._name = attrs['name'] + self._task_id = attrs['task_id'] + self._flags.parse(**attrs.get('flags', {})) + self._name_task_id = '{}-{}'.format( + self._name, self._task_id[:self.SHORT_TASK_ID_LEN]) + + def split_host_name(self, name): + if (isinstance(name, six.string_types) + and self._host_name_separator in name): + return tuple(name.split(self._host_name_separator, 1)) + return None, None + + def read_pod_file(self, attrs): + self.file_path = file_path = attrs.get("file", "pod.yaml") + try: + cfg = yaml_loader.read_yaml_file(self.file_path) + except IOError as io_error: + if io_error.errno != errno.ENOENT: + raise + + self.file_path = os.path.join(YARDSTICK_ROOT_PATH, file_path) + cfg = yaml_loader.read_yaml_file(self.file_path) + + for node in cfg["nodes"]: + node["ctx_type"] = self.__context_type__ + + self.nodes.extend(cfg["nodes"]) + self.controllers.extend([node for node in cfg["nodes"] + if node.get("role") == "Controller"]) + self.computes.extend([node for node in cfg["nodes"] + if node.get("role") == "Compute"]) + self.baremetals.extend([node for node in cfg["nodes"] + if node.get("role") == "Baremetal"]) + return cfg + + @property + def name(self): + if self._flags.no_setup or self._flags.no_teardown: + return self._name + else: + return self._name_task_id + + @property + def assigned_name(self): + return self._name + + @property + def host_name_separator(self): + return self._host_name_separator @staticmethod def get_cls(context_type): @@ -84,6 +159,25 @@ class Context(object): attr_name) @staticmethod + def get_physical_nodes(): + """return physical node names for all contexts""" + physical_nodes = {} + for context in Context.list: + nodes = context._get_physical_nodes() + physical_nodes.update({context._name: nodes}) + + return physical_nodes + + @staticmethod + def get_physical_node_from_server(server_name): + """return physical nodes for all contexts""" + context = Context.get_context_from_server(server_name) + if context == None: + return None + + return context._get_physical_node_for_server(server_name) + + @staticmethod def get_context_from_server(attr_name): """lookup context info by name from node config attr_name: either a name of the node created by yardstick or a dict @@ -112,3 +206,15 @@ class Context(object): except StopIteration: raise ValueError("context not found for server %r" % attr_name) + + @abc.abstractmethod + def _get_physical_nodes(self): + """return the list of physical nodes in context""" + + @abc.abstractmethod + def _get_physical_node_for_server(self, server_name): + """ Find physical node for given server + + :param server_name: (string) Server name in scenario + :return string: <node_name>.<context_name> + """ diff --git a/yardstick/benchmark/contexts/dummy.py b/yardstick/benchmark/contexts/dummy.py index 8ae4b65b8..9faca4c63 100644 --- a/yardstick/benchmark/contexts/dummy.py +++ b/yardstick/benchmark/contexts/dummy.py @@ -7,36 +7,35 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import absolute_import -import logging +from yardstick.benchmark import contexts +from yardstick.benchmark.contexts import base -from yardstick.benchmark.contexts.base import Context +class DummyContext(base.Context): + """Class that handle dummy info. -LOG = logging.getLogger(__name__) + This class is also used to test the abstract class Context because it + provides a minimal concrete implementation of a subclass. + """ - -class DummyContext(Context): - """Class that handle dummy info""" - - __context_type__ = "Dummy" - - def __init__(self): - super(DummyContext, self).__init__() - - def init(self, attrs): - pass + __context_type__ = contexts.CONTEXT_DUMMY def deploy(self): - """don't need to deploy""" + """Don't need to deploy""" pass def undeploy(self): - """don't need to undeploy""" - super(DummyContext, self).undeploy() + """Don't need to undeploy""" + pass def _get_server(self, attr_name): return None def _get_network(self, attr_name): return None + + def _get_physical_nodes(self): + return None + + def _get_physical_node_for_server(self, server_name): + return None 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 diff --git a/yardstick/benchmark/contexts/kubernetes.py b/yardstick/benchmark/contexts/kubernetes.py index 2334e5076..e1553c72b 100644 --- a/yardstick/benchmark/contexts/kubernetes.py +++ b/yardstick/benchmark/contexts/kubernetes.py @@ -7,51 +7,57 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import absolute_import +import collections import logging -import time import pkg_resources +import time import paramiko -from yardstick.benchmark.contexts.base import Context -from yardstick.orchestrator.kubernetes import KubernetesTemplate +from yardstick.benchmark import contexts +from yardstick.benchmark.contexts import base as ctx_base +from yardstick.benchmark.contexts import model +from yardstick.common import constants +from yardstick.common import exceptions from yardstick.common import kubernetes_utils as k8s_utils from yardstick.common import utils +from yardstick.orchestrator import kubernetes + LOG = logging.getLogger(__name__) BITS_LENGTH = 2048 -class KubernetesContext(Context): +class KubernetesContext(ctx_base.Context): """Class that handle nodes info""" - __context_type__ = "Kubernetes" + __context_type__ = contexts.CONTEXT_KUBERNETES def __init__(self): - self.name = '' self.ssh_key = '' self.key_path = '' self.public_key_path = '' self.template = None - - super(KubernetesContext, self).__init__() + super(KubernetesContext, self).__init__(host_name_separator='-') def init(self, attrs): - self.name = attrs.get('name', '') - - template_cfg = attrs.get('servers', {}) - self.template = KubernetesTemplate(self.name, template_cfg) + super(KubernetesContext, self).init(attrs) + networks = attrs.get('networks', {}) + self.template = kubernetes.KubernetesTemplate(self.name, attrs) self.ssh_key = '{}-key'.format(self.name) - self.key_path = self._get_key_path() self.public_key_path = '{}.pub'.format(self.key_path) + self._networks = collections.OrderedDict( + (net_name, model.Network(net_name, self, network)) + for net_name, network in networks.items()) def deploy(self): LOG.info('Creating ssh key') self._set_ssh_key() + self._create_crd() + self._create_networks() LOG.info('Launch containers') self._create_rcs() self._create_services() @@ -65,6 +71,8 @@ class KubernetesContext(Context): self._delete_rcs() self._delete_pods() self._delete_services() + self._delete_networks() + self._delete_crd() super(KubernetesContext, self).undeploy() @@ -91,7 +99,7 @@ class KubernetesContext(Context): obj.delete() def _create_rcs(self): - for obj in self.template.k8s_objs: + for obj in self.template.rc_objs: self._create_rc(obj.get_template()) def _create_rc(self, template): @@ -102,14 +110,34 @@ class KubernetesContext(Context): self._delete_rc(rc) def _delete_rc(self, rc): - k8s_utils.delete_replication_controller(rc) + k8s_utils.delete_replication_controller(rc, skip_codes=[404]) def _delete_pods(self): for pod in self.template.pods: self._delete_pod(pod) def _delete_pod(self, pod): - k8s_utils.delete_pod(pod) + k8s_utils.delete_pod(pod, skip_codes=[404]) + + def _create_crd(self): + LOG.info('Create Custom Resource Definition elements') + for crd in self.template.crd: + crd.create() + + def _delete_crd(self): + LOG.info('Delete Custom Resource Definition elements') + for crd in self.template.crd: + crd.delete() + + def _create_networks(self): # pragma: no cover + LOG.info('Create Network elements') + for net in self.template.network_objs: + net.create() + + def _delete_networks(self): # pragma: no cover + LOG.info('Create Network elements') + for net in self.template.network_objs: + net.delete() def _get_key_path(self): task_id = self.name.split('-')[-1] @@ -131,27 +159,76 @@ class KubernetesContext(Context): k8s_utils.create_config_map(self.ssh_key, {'authorized_keys': key}) def _delete_ssh_key(self): - k8s_utils.delete_config_map(self.ssh_key) + k8s_utils.delete_config_map(self.ssh_key, skip_codes=[404]) utils.remove_file(self.key_path) utils.remove_file(self.public_key_path) def _get_server(self, name): - service_name = '{}-service'.format(name) - service = k8s_utils.get_service_by_name(service_name).ports[0] - - host = { - 'name': service.name, + node_ports = self._get_service_ports(name) + for sn_port in (sn_port for sn_port in node_ports + if sn_port['port'] == constants.SSH_PORT): + node_port = sn_port['node_port'] + break + else: + raise exceptions.KubernetesSSHPortNotDefined() + + return { + 'name': name, 'ip': self._get_node_ip(), 'private_ip': k8s_utils.get_pod_by_name(name).status.pod_ip, - 'ssh_port': service.node_port, + 'ssh_port': node_port, 'user': 'root', 'key_filename': self.key_path, + 'interfaces': self._get_interfaces(name), + 'service_ports': node_ports } - return host + def _get_network(self, net_name): + """Retrieves the network object, searching by name + + :param net_name: (str) replication controller name + :return: (dict) network information (name) + """ + network = self._networks.get(net_name) + if not network: + return + return {'name': net_name} + + def _get_interfaces(self, rc_name): + """Retrieves the network list of a replication controller + + :param rc_name: (str) replication controller name + :return: (dict) names and information of the networks used in this + replication controller; those networks must be defined in the + Kubernetes cluster + """ + rc = self.template.get_rc_by_name(rc_name) + if not rc: + return {} + return {name: {'network_name': name, + 'local_mac': None, + 'local_ip': None} + for name in rc.networks} def _get_node_ip(self): return k8s_utils.get_node_list().items[0].status.addresses[0].address - def _get_network(self, attr_name): + def _get_physical_nodes(self): return None + + def _get_physical_node_for_server(self, server_name): + return None + + def _get_service_ports(self, name): + service_name = '{}-service'.format(name) + service = k8s_utils.get_service_by_name(service_name) + if not service: + raise exceptions.KubernetesServiceObjectNotDefined() + ports = [] + for port in service.ports: + ports.append({'name': port.name, + 'node_port': port.node_port, + 'port': port.port, + 'protocol': port.protocol, + 'target_port': port.target_port}) + return ports diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py index ae56066ee..a55c11f79 100644 --- a/yardstick/benchmark/contexts/model.py +++ b/yardstick/benchmark/contexts/model.py @@ -18,6 +18,8 @@ import logging from collections import Mapping from six.moves import range +from yardstick.common import constants as consts + LOG = logging.getLogger(__name__) @@ -132,11 +134,28 @@ class Network(Object): if self.gateway_ip is None: self.gateway_ip = "null" - if "external_network" in attrs: - self.router = Router("router", self.name, - context, attrs["external_network"]) - - Network.list.append(self) + self.net_flags = attrs.get('net_flags', {}) + if self.is_existing(): + self.subnet = attrs.get('subnet') + if not self.subnet: + raise Warning('No subnet set in existing netwrok!') + else: + if "external_network" in attrs: + self.router = Router("router", self.name, + context, attrs["external_network"]) + Network.list.append(self) + + def is_existing(self): + net_is_existing = self.net_flags.get(consts.IS_EXISTING) + if net_is_existing and not isinstance(net_is_existing, bool): + raise SyntaxError('Network flags should be bool type!') + return net_is_existing + + def is_public(self): + net_is_public = self.net_flags.get(consts.IS_PUBLIC) + if net_is_public and not isinstance(net_is_public, bool): + raise SyntaxError('Network flags should be bool type!') + return net_is_public def has_route_to(self, network_name): """determines if this network has a route to the named network""" @@ -302,10 +321,13 @@ class Server(Object): # pragma: no cover # otherwise add a port for every network with port name as network name else: ports = [network.name] + net_flags = network.net_flags for port in ports: port_name = "{0}-{1}-port".format(server_name, port) - self.ports.setdefault(network.name, []).append( - {"stack_name": port_name, "port": port}) + port_info = {"stack_name": port_name, "port": port} + if net_flags: + port_info['net_flags'] = net_flags + self.ports.setdefault(network.name, []).append(port_info) # we can't use secgroups if port_security_enabled is False if network.port_security_enabled is False: sec_group_id = None @@ -314,11 +336,14 @@ class Server(Object): # pragma: no cover sec_group_id = self.secgroup_name # don't refactor to pass in network object, that causes JSON # circular ref encode errors - template.add_port(port_name, network.stack_name, network.subnet_stack_name, - network.vnic_type, sec_group_id=sec_group_id, + template.add_port(port_name, network, + sec_group_id=sec_group_id, provider=network.provider, allowed_address_pairs=network.allowed_address_pairs) - port_name_list.append(port_name) + if network.is_public(): + port_name_list.insert(0, port_name) + else: + port_name_list.append(port_name) if self.floating_ip: external_network = self.floating_ip["external_network"] diff --git a/yardstick/benchmark/contexts/node.py b/yardstick/benchmark/contexts/node.py index ffc82c8ed..d233e02ae 100644 --- a/yardstick/benchmark/contexts/node.py +++ b/yardstick/benchmark/contexts/node.py @@ -7,8 +7,6 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import absolute_import -import errno import subprocess import os import collections @@ -19,10 +17,11 @@ import six import pkg_resources from yardstick import ssh +from yardstick.benchmark import contexts from yardstick.benchmark.contexts.base import Context from yardstick.common.constants import ANSIBLE_DIR, YARDSTICK_ROOT_PATH from yardstick.common.ansible_common import AnsibleCommon -from yardstick.common.yaml_loader import yaml_load +from yardstick.common.exceptions import ContextUpdateCollectdForNodeError LOG = logging.getLogger(__name__) @@ -32,10 +31,9 @@ DEFAULT_DISPATCH = 'script' class NodeContext(Context): """Class that handle nodes info""" - __context_type__ = "Node" + __context_type__ = contexts.CONTEXT_NODE def __init__(self): - self.name = None self.file_path = None self.nodes = [] self.networks = {} @@ -50,39 +48,11 @@ class NodeContext(Context): } super(NodeContext, self).__init__() - def read_config_file(self): - """Read from config file""" - - with open(self.file_path) as stream: - LOG.info("Parsing pod file: %s", self.file_path) - cfg = yaml_load(stream) - return cfg - def init(self, attrs): """initializes itself from the supplied arguments""" - self.name = attrs["name"] - self.file_path = file_path = attrs.get("file", "pod.yaml") + super(NodeContext, self).init(attrs) - try: - cfg = self.read_config_file() - except IOError as io_error: - if io_error.errno != errno.ENOENT: - raise - - self.file_path = os.path.join(YARDSTICK_ROOT_PATH, file_path) - cfg = self.read_config_file() - - self.nodes.extend(cfg["nodes"]) - self.controllers.extend([node for node in cfg["nodes"] - if node.get("role") == "Controller"]) - self.computes.extend([node for node in cfg["nodes"] - if node.get("role") == "Compute"]) - self.baremetals.extend([node for node in cfg["nodes"] - if node.get("role") == "Baremetal"]) - LOG.debug("Nodes: %r", self.nodes) - LOG.debug("Controllers: %r", self.controllers) - LOG.debug("Computes: %r", self.computes) - LOG.debug("BareMetals: %r", self.baremetals) + cfg = self.read_pod_file(attrs) self.env = attrs.get('env', {}) self.attrs = attrs @@ -135,11 +105,37 @@ class NodeContext(Context): playbook = os.path.join(ANSIBLE_DIR, playbook) return playbook + def _get_physical_nodes(self): + return self.nodes + + def _get_physical_node_for_server(self, server_name): + + node_name, context_name = self.split_host_name(server_name) + + if context_name is None or self.name != context_name: + return None + + for n in (n for n in self.nodes if n["name"] == node_name): + return "{}.{}".format(n["name"], self._name) + + return None + + def update_collectd_options_for_node(self, options, attr_name): + node_name, _ = self.split_host_name(attr_name) + + matching_nodes = (n for n in self.nodes if n["name"] == node_name) + try: + node = next(matching_nodes) + except StopIteration: + raise ContextUpdateCollectdForNodeError(attr_name=attr_name) + + node["collectd"] = options + def _get_server(self, attr_name): """lookup server info by name from context attr_name: a name for a server listed in nodes config file """ - node_name, name = self.split_name(attr_name) + node_name, name = self.split_host_name(attr_name) if name is None or self.name != name: return None @@ -157,7 +153,7 @@ class NodeContext(Context): except StopIteration: pass else: - raise ValueError("Duplicate nodes!!! Nodes: %s %s", + raise ValueError("Duplicate nodes!!! Nodes: %s %s" % (node, duplicate)) node["name"] = attr_name @@ -204,7 +200,7 @@ class NodeContext(Context): self.client._put_file_shell(script_file, '~/{}'.format(script)) cmd = 'sudo bash {} {}'.format(script, options) - status, stdout, stderr = self.client.execute(cmd) + status, _, stderr = self.client.execute(cmd) if status: raise RuntimeError(stderr) diff --git a/yardstick/benchmark/contexts/standalone/model.py b/yardstick/benchmark/contexts/standalone/model.py index 85ae14b1d..a15426872 100644 --- a/yardstick/benchmark/contexts/standalone/model.py +++ b/yardstick/benchmark/contexts/standalone/model.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import import os import re import time @@ -25,17 +24,19 @@ from netaddr import IPNetwork import xml.etree.ElementTree as ET from yardstick import ssh -from yardstick.common.constants import YARDSTICK_ROOT_PATH -from yardstick.common.yaml_loader import yaml_load +from yardstick.common import constants +from yardstick.common import exceptions +from yardstick.common import utils as common_utils +from yardstick.common import yaml_loader from yardstick.network_services.utils import PciAddress from yardstick.network_services.helpers.cpu import CpuSysCores -from yardstick.common.utils import write_file + LOG = logging.getLogger(__name__) VM_TEMPLATE = """ <domain type="kvm"> - <name>{vm_name}</name> + <name>{vm_name}</name> <uuid>{random_uuid}</uuid> <memory unit="MB">{memory}</memory> <currentMemory unit="MB">{memory}</currentMemory> @@ -45,7 +46,7 @@ VM_TEMPLATE = """ <vcpu cpuset='{cpuset}'>{vcpu}</vcpu> {cputune} <os> - <type arch="x86_64" machine="pc-i440fx-utopic">hvm</type> + <type arch="x86_64" machine="{machine}">hvm</type> <boot dev="hd" /> </os> <features> @@ -80,9 +81,39 @@ VM_TEMPLATE = """ <source bridge="br-int" /> <model type='virtio'/> </interface> - </devices> + <serial type='pty'> + <target port='0'/> + </serial> + <console type='pty'> + <target type='serial' port='0'/> + </console> + </devices> </domain> """ + +USER_DATA_TEMPLATE = """ +cat > {user_file} <<EOF +#cloud-config +preserve_hostname: false +hostname: {host} +users: +{user_config} +EOF +""" + +NETWORK_DATA_TEMPLATE = """ +cat > {network_file} <<EOF +#cloud-config +version: 2 +ethernets: + ens3: + match: + macaddress: {mac_address} + addresses: + - {ip_address} +EOF +""" + WAIT_FOR_BOOT = 30 @@ -100,12 +131,17 @@ class Libvirt(object): @staticmethod def virsh_create_vm(connection, cfg): - err = connection.execute("virsh create %s" % cfg)[0] - LOG.info("VM create status: %s", err) + LOG.info('VM create, XML config: %s', cfg) + status, _, error = connection.execute('virsh create %s' % cfg) + if status: + raise exceptions.LibvirtCreateError(error=error) @staticmethod def virsh_destroy_vm(vm_name, connection): - connection.execute("virsh destroy %s" % vm_name) + LOG.info('VM destroy, VM name: %s', vm_name) + status, _, error = connection.execute('virsh destroy %s' % vm_name) + if status: + LOG.warning('Error destroying VM %s. Error: %s', vm_name, error) @staticmethod def _add_interface_address(interface, pci_address): @@ -126,7 +162,8 @@ class Libvirt(object): return vm_pci @classmethod - def add_ovs_interface(cls, vpath, port_num, vpci, vports_mac, xml): + def add_ovs_interface(cls, vpath, port_num, vpci, vports_mac, xml_str, + queues): """Add a DPDK OVS 'interface' XML node in 'devices' node <devices> @@ -150,7 +187,7 @@ class Libvirt(object): vhost_path = ('{0}/var/run/openvswitch/dpdkvhostuser{1}'. format(vpath, port_num)) - root = ET.parse(xml) + root = ET.fromstring(xml_str) pci_address = PciAddress(vpci.strip()) device = root.find('devices') @@ -168,17 +205,17 @@ class Libvirt(object): model.set('type', 'virtio') driver = ET.SubElement(interface, 'driver') - driver.set('queues', '4') + driver.set('queues', str(queues)) host = ET.SubElement(driver, 'host') host.set('mrg_rxbuf', 'off') cls._add_interface_address(interface, pci_address) - root.write(xml) + return ET.tostring(root) @classmethod - def add_sriov_interfaces(cls, vm_pci, vf_pci, vf_mac, xml): + def add_sriov_interfaces(cls, vm_pci, vf_pci, vf_mac, xml_str): """Add a SR-IOV 'interface' XML node in 'devices' node <devices> @@ -201,7 +238,7 @@ class Libvirt(object): -sr_iov-how_sr_iov_libvirt_works """ - root = ET.parse(xml) + root = ET.fromstring(xml_str) device = root.find('devices') interface = ET.SubElement(device, 'interface') @@ -212,27 +249,53 @@ class Libvirt(object): mac.set('address', vf_mac) source = ET.SubElement(interface, 'source') - addr = ET.SubElement(source, 'address') pci_address = PciAddress(vf_pci.strip()) - cls._add_interface_address(addr, pci_address) + cls._add_interface_address(source, pci_address) pci_vm_address = PciAddress(vm_pci.strip()) cls._add_interface_address(interface, pci_vm_address) - root.write(xml) + return ET.tostring(root) @staticmethod - def create_snapshot_qemu(connection, index, vm_image): - # build snapshot image - image = "/var/lib/libvirt/images/%s.qcow2" % index - connection.execute("rm %s" % image) - qemu_template = "qemu-img create -f qcow2 -o backing_file=%s %s" - connection.execute(qemu_template % (vm_image, image)) + def create_snapshot_qemu(connection, index, base_image): + """Create the snapshot image for a VM using a base image - return image + :param connection: SSH connection to the remote host + :param index: index of the VM to be spawn + :param base_image: path of the VM base image in the remote host + :return: snapshot image path + """ + vm_image = '/var/lib/libvirt/images/%s.qcow2' % index + connection.execute('rm -- "%s"' % vm_image) + status, _, _ = connection.execute('test -r %s' % base_image) + if status: + if not os.access(base_image, os.R_OK): + raise exceptions.LibvirtQemuImageBaseImageNotPresent( + vm_image=vm_image, base_image=base_image) + # NOTE(ralonsoh): done in two steps to avoid root permission + # issues. + LOG.info('Copy %s from execution host to remote host', base_image) + file_name = os.path.basename(os.path.normpath(base_image)) + connection.put_file(base_image, '/tmp/%s' % file_name) + status, _, error = connection.execute( + 'mv -- "/tmp/%s" "%s"' % (file_name, base_image)) + if status: + raise exceptions.LibvirtQemuImageCreateError( + vm_image=vm_image, base_image=base_image, error=error) + + LOG.info('Convert image %s to %s', base_image, vm_image) + qemu_cmd = ('qemu-img create -f qcow2 -o backing_file=%s %s' % + (base_image, vm_image)) + status, _, error = connection.execute(qemu_cmd) + if status: + raise exceptions.LibvirtQemuImageCreateError( + vm_image=vm_image, base_image=base_image, error=error) + return vm_image @classmethod - def build_vm_xml(cls, connection, flavor, cfg, vm_name, index): + def build_vm_xml(cls, connection, flavor, vm_name, index, cdrom_img): + """Build the XML from the configuration parameters""" memory = flavor.get('ram', '4096') extra_spec = flavor.get('extra_specs', {}) cpu = extra_spec.get('hw:cpu_cores', '2') @@ -244,6 +307,7 @@ class Libvirt(object): cpuset = Libvirt.pin_vcpu_for_perf(connection, hw_socket) cputune = extra_spec.get('cputune', '') + machine = extra_spec.get('machine_type', 'pc-i440fx-xenial') mac = StandaloneContextHelper.get_mac_address(0x00) image = cls.create_snapshot_qemu(connection, index, flavor.get("images", None)) @@ -254,11 +318,13 @@ class Libvirt(object): memory=memory, vcpu=vcpu, cpu=cpu, numa_cpus=numa_cpus, socket=socket, threads=threads, - vm_image=image, cpuset=cpuset, cputune=cputune) + vm_image=image, cpuset=cpuset, + machine=machine, cputune=cputune) - write_file(cfg, vm_xml) + # Add CD-ROM device + vm_xml = Libvirt.add_cdrom(cdrom_img, vm_xml) - return [vcpu, mac] + return vm_xml, mac @staticmethod def update_interrupts_hugepages_perf(connection): @@ -278,6 +344,82 @@ class Libvirt(object): cpuset = "%s,%s" % (cores, threads) return cpuset + @classmethod + def write_file(cls, file_name, xml_str): + """Dump a XML string to a file""" + root = ET.fromstring(xml_str) + et = ET.ElementTree(element=root) + et.write(file_name, encoding='utf-8', method='xml') + + @classmethod + def add_cdrom(cls, file_path, xml_str): + """Add a CD-ROM disk XML node in 'devices' node + + <devices> + <disk type='file' device='cdrom'> + <driver name='qemu' type='raw'/> + <source file='/var/lib/libvirt/images/data.img'/> + <target dev='hdb'/> + <readonly/> + </disk> + ... + </devices> + """ + + root = ET.fromstring(xml_str) + device = root.find('devices') + + disk = ET.SubElement(device, 'disk') + disk.set('type', 'file') + disk.set('device', 'cdrom') + + driver = ET.SubElement(disk, 'driver') + driver.set('name', 'qemu') + driver.set('type', 'raw') + + source = ET.SubElement(disk, 'source') + source.set('file', file_path) + + target = ET.SubElement(disk, 'target') + target.set('dev', 'hdb') + + ET.SubElement(disk, 'readonly') + return ET.tostring(root) + + @staticmethod + def gen_cdrom_image(connection, file_path, vm_name, vm_user, key_filename, mac, ip): + """Generate ISO image for CD-ROM """ + + user_config = [" - name: {user_name}", + " ssh_authorized_keys:", + " - {pub_key_str}"] + if vm_user != "root": + user_config.append(" sudo: ALL=(ALL) NOPASSWD:ALL") + + meta_data = "/tmp/meta-data" + user_data = "/tmp/user-data" + network_data = "/tmp/network-config" + with open(".".join([key_filename, "pub"]), "r") as pub_key_file: + pub_key_str = pub_key_file.read().rstrip() + user_conf = os.linesep.join(user_config).format(pub_key_str=pub_key_str, user_name=vm_user) + + cmd_lst = [ + "touch %s" % meta_data, + USER_DATA_TEMPLATE.format(user_file=user_data, host=vm_name, user_config=user_conf), + NETWORK_DATA_TEMPLATE.format(network_file=network_data, mac_address=mac, + ip_address=ip), + "genisoimage -output {0} -volid cidata -joliet -r {1} {2} {3}".format(file_path, + meta_data, + user_data, + network_data), + "rm {0} {1} {2}".format(meta_data, user_data, network_data), + ] + for cmd in cmd_lst: + LOG.info(cmd) + status, _, error = connection.execute(cmd) + if status: + raise exceptions.LibvirtQemuImageCreateError(error=error) + class StandaloneContextHelper(object): """ This class handles all the common code for standalone @@ -287,8 +429,9 @@ class StandaloneContextHelper(object): super(StandaloneContextHelper, self).__init__() @staticmethod - def install_req_libs(connection, extra_pkgs=[]): - pkgs = ["qemu-kvm", "libvirt-bin", "bridge-utils", "numactl", "fping"] + def install_req_libs(connection, extra_pkgs=None): + extra_pkgs = extra_pkgs or [] + pkgs = ["qemu-kvm", "libvirt-bin", "bridge-utils", "numactl", "fping", "genisoimage"] pkgs.extend(extra_pkgs) cmd_template = "dpkg-query -W --showformat='${Status}\\n' \"%s\"|grep 'ok installed'" for pkg in pkgs: @@ -304,7 +447,7 @@ class StandaloneContextHelper(object): return driver @classmethod - def get_nic_details(cls, connection, networks, dpdk_nic_bind): + def get_nic_details(cls, connection, networks, dpdk_devbind): for key, ports in networks.items(): if key == "mgmt": continue @@ -314,11 +457,11 @@ class StandaloneContextHelper(object): driver = cls.get_kernel_module(connection, phy_ports, phy_driver) # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe - bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" + bind_cmd = "{dpdk_devbind} --force -b {driver} {port}" lshw_cmd = "lshw -c network -businfo | grep '{port}'" link_show_cmd = "ip -s link show {interface}" - cmd = bind_cmd.format(dpdk_nic_bind=dpdk_nic_bind, + cmd = bind_cmd.format(dpdk_devbind=dpdk_devbind, driver=driver, port=ports['phy_port']) connection.execute(cmd) @@ -351,25 +494,18 @@ class StandaloneContextHelper(object): return pf_vfs - def read_config_file(self): - """Read from config file""" - - with open(self.file_path) as stream: - LOG.info("Parsing pod file: %s", self.file_path) - cfg = yaml_load(stream) - return cfg - def parse_pod_file(self, file_path, nfvi_role='Sriov'): self.file_path = file_path nodes = [] nfvi_host = [] try: - cfg = self.read_config_file() + cfg = yaml_loader.read_yaml_file(self.file_path) except IOError as io_error: if io_error.errno != errno.ENOENT: raise - self.file_path = os.path.join(YARDSTICK_ROOT_PATH, file_path) - cfg = self.read_config_file() + self.file_path = os.path.join(constants.YARDSTICK_ROOT_PATH, + file_path) + cfg = yaml_loader.read_yaml_file(self.file_path) nodes.extend([node for node in cfg["nodes"] if str(node["role"]) != nfvi_role]) nfvi_host.extend([node for node in cfg["nodes"] if str(node["role"]) == nfvi_role]) @@ -419,8 +555,41 @@ class StandaloneContextHelper(object): ip = cls.get_mgmt_ip(connection, node["mac"], mgmtip, node) if ip: node["ip"] = ip + client = ssh.SSH.from_node(node) + LOG.debug("OS version: %s", + common_utils.get_os_version(client)) + LOG.debug("Kernel version: %s", + common_utils.get_kernel_version(client)) + vnfs_data = common_utils.get_sample_vnf_info(client) + for vnf_name, vnf_data in vnfs_data.items(): + LOG.debug("VNF name: '%s', commit ID/branch: '%s'", + vnf_name, vnf_data["branch_commit"]) + LOG.debug("%s", vnf_data["md5_result"]) return nodes + @classmethod + def check_update_key(cls, connection, node, vm_name, id_name, cdrom_img, mac): + # Generate public/private keys if private key file is not provided + user_name = node.get('user') + if not user_name: + node['user'] = 'root' + user_name = node.get('user') + if not node.get('key_filename'): + key_filename = ''.join( + [constants.YARDSTICK_ROOT_PATH, + 'yardstick/resources/files/yardstick_key-', + id_name, '-', vm_name]) + ssh.SSH.gen_keys(key_filename) + node['key_filename'] = key_filename + # Update image with public key + key_filename = node.get('key_filename') + ip_netmask = "{0}/{1}".format(node.get('ip'), node.get('netmask')) + ip_netmask = "{0}/{1}".format(node.get('ip'), + IPNetwork(ip_netmask).prefixlen) + Libvirt.gen_cdrom_image(connection, cdrom_img, vm_name, user_name, key_filename, mac, + ip_netmask) + return node + class Server(object): """ This class handles geting vnf nodes @@ -433,7 +602,7 @@ class Server(object): for key, vfs in vnf["network_ports"].items(): if key == "mgmt": - mgmtip = str(IPNetwork(vfs['cidr']).ip) + mgmt_cidr = IPNetwork(vfs['cidr']) continue vf = ports[vfs[0]] @@ -450,14 +619,15 @@ class Server(object): }) index = index + 1 - return mgmtip, interfaces + return mgmt_cidr, interfaces @classmethod def generate_vnf_instance(cls, flavor, ports, ip, key, vnf, mac): - mgmtip, interfaces = cls.build_vnf_interfaces(vnf, ports) + mgmt_cidr, interfaces = cls.build_vnf_interfaces(vnf, ports) result = { - "ip": mgmtip, + "ip": str(mgmt_cidr.ip), + "netmask": str(mgmt_cidr.netmask), "mac": mac, "host": ip, "user": flavor.get('user', 'root'), @@ -500,7 +670,7 @@ class OvsDeploy(object): StandaloneContextHelper.install_req_libs(self.connection, pkgs) def ovs_deploy(self): - ovs_deploy = os.path.join(YARDSTICK_ROOT_PATH, + ovs_deploy = os.path.join(constants.YARDSTICK_ROOT_PATH, "yardstick/resources/scripts/install/", self.OVS_DEPLOY_SCRIPT) if os.path.isfile(ovs_deploy): @@ -516,4 +686,6 @@ class OvsDeploy(object): cmd = "sudo -E %s --ovs='%s' --dpdk='%s' -p='%s'" % (remote_ovs_deploy, ovs, dpdk, http_proxy) - self.connection.execute(cmd) + exit_status, _, stderr = self.connection.execute(cmd) + if exit_status: + raise exceptions.OVSDeployError(stderr=stderr) diff --git a/yardstick/benchmark/contexts/standalone/ovs_dpdk.py b/yardstick/benchmark/contexts/standalone/ovs_dpdk.py index 3755b84e9..c6e19f614 100644 --- a/yardstick/benchmark/contexts/standalone/ovs_dpdk.py +++ b/yardstick/benchmark/contexts/standalone/ovs_dpdk.py @@ -12,33 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import absolute_import -import os -import logging +import io import collections +import logging +import os +import re import time -from collections import OrderedDict - from yardstick import ssh +from yardstick.benchmark import contexts +from yardstick.benchmark.contexts import base +from yardstick.benchmark.contexts.standalone import model +from yardstick.common import exceptions +from yardstick.common import utils as common_utils +from yardstick.network_services import utils from yardstick.network_services.utils import get_nsb_option -from yardstick.network_services.utils import provision_tool -from yardstick.benchmark.contexts.base import Context -from yardstick.benchmark.contexts.standalone.model import Libvirt -from yardstick.benchmark.contexts.standalone.model import StandaloneContextHelper -from yardstick.benchmark.contexts.standalone.model import Server -from yardstick.benchmark.contexts.standalone.model import OvsDeploy -from yardstick.network_services.utils import PciAddress + LOG = logging.getLogger(__name__) +MAIN_BRIDGE = 'br0' -class OvsDpdkContext(Context): + +class OvsDpdkContext(base.Context): """ This class handles OVS standalone nodes - VM running on Non-Managed NFVi Configuration: ovs_dpdk """ - __context_type__ = "StandaloneOvsDpdk" + __context_type__ = contexts.CONTEXT_STANDALONEOVSDPDK SUPPORTED_OVS_TO_DPDK_MAP = { '2.6.0': '16.07.1', @@ -46,36 +47,42 @@ class OvsDpdkContext(Context): '2.7.0': '16.11.1', '2.7.1': '16.11.2', '2.7.2': '16.11.3', - '2.8.0': '17.05.2' + '2.8.0': '17.05.2', + '2.8.1': '17.05.2' } DEFAULT_OVS = '2.6.0' - - PKILL_TEMPLATE = "pkill %s %s" + CMD_TIMEOUT = 30 + DEFAULT_USER_PATH = '/usr/local' def __init__(self): self.file_path = None self.sriov = [] self.first_run = True - self.dpdk_nic_bind = "" + self.dpdk_devbind = os.path.join(get_nsb_option('bin_path'), + 'dpdk-devbind.py') self.vm_names = [] - self.name = None self.nfvi_host = [] self.nodes = [] self.networks = {} self.attrs = {} self.vm_flavor = None self.servers = None - self.helper = StandaloneContextHelper() - self.vnf_node = Server() + self.helper = model.StandaloneContextHelper() + self.vnf_node = model.Server() self.ovs_properties = {} self.wait_for_vswitchd = 10 super(OvsDpdkContext, self).__init__() + def get_dpdk_socket_mem_size(self, socket_id): + """Get the size of OvS DPDK socket memory (Mb)""" + ram = self.ovs_properties.get("ram", {}) + return ram.get('socket_%d' % (socket_id), 2048) + def init(self, attrs): """initializes itself from the supplied arguments""" + super(OvsDpdkContext, self).init(attrs) - self.name = attrs["name"] self.file_path = attrs.get("file", "pod.yaml") self.nodes, self.nfvi_host, self.host_mgmt = \ @@ -94,34 +101,32 @@ class OvsDpdkContext(Context): LOG.debug("Networks: %r", self.networks) def setup_ovs(self): - vpath = self.ovs_properties.get("vpath", "/usr/local") - xargs_kill_cmd = self.PKILL_TEMPLATE % ('-9', 'ovs') - + """Initialize OVS-DPDK""" + vpath = self.ovs_properties.get('vpath', self.DEFAULT_USER_PATH) create_from = os.path.join(vpath, 'etc/openvswitch/conf.db') create_to = os.path.join(vpath, 'share/openvswitch/vswitch.ovsschema') cmd_list = [ - "chmod 0666 /dev/vfio/*", - "chmod a+x /dev/vfio", - "pkill -9 ovs", - xargs_kill_cmd, - "killall -r 'ovs*'", - "mkdir -p {0}/etc/openvswitch".format(vpath), - "mkdir -p {0}/var/run/openvswitch".format(vpath), - "rm {0}/etc/openvswitch/conf.db".format(vpath), - "ovsdb-tool create {0} {1}".format(create_from, create_to), - "modprobe vfio-pci", - "chmod a+x /dev/vfio", - "chmod 0666 /dev/vfio/*", + 'killall -r "ovs.*" -q | true', + 'mkdir -p {0}/etc/openvswitch'.format(vpath), + 'mkdir -p {0}/var/run/openvswitch'.format(vpath), + 'rm {0}/etc/openvswitch/conf.db | true'.format(vpath), + 'ovsdb-tool create {0} {1}'.format(create_from, create_to), + 'modprobe vfio-pci', + 'chmod a+x /dev/vfio', + 'chmod 0666 /dev/vfio/*', ] + + bind_cmd = '%s --force -b vfio-pci {port}' % self.dpdk_devbind + for port in self.networks.values(): + cmd_list.append(bind_cmd.format(port=port.get('phy_port'))) + for cmd in cmd_list: - self.connection.execute(cmd) - bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" - phy_driver = "vfio-pci" - for _, port in self.networks.items(): - vpci = port.get("phy_port") - self.connection.execute(bind_cmd.format(dpdk_nic_bind=self.dpdk_nic_bind, - driver=phy_driver, port=vpci)) + LOG.info(cmd) + exit_status, _, stderr = self.connection.execute( + cmd, timeout=self.CMD_TIMEOUT) + if exit_status: + raise exceptions.OVSSetupError(command=cmd, error=stderr) def start_ovs_serverswitch(self): vpath = self.ovs_properties.get("vpath") @@ -134,9 +139,6 @@ class OvsDpdkContext(Context): if pmd_cpu_mask: pmd_mask = pmd_cpu_mask - socket0 = self.ovs_properties.get("ram", {}).get("socket_0", "2048") - socket1 = self.ovs_properties.get("ram", {}).get("socket_1", "2048") - ovs_other_config = "ovs-vsctl {0}set Open_vSwitch . other_config:{1}" detach_cmd = "ovs-vswitchd unix:{0}{1} --pidfile --detach --log-file={2}" @@ -144,16 +146,23 @@ class OvsDpdkContext(Context): if lcore_mask: lcore_mask = ovs_other_config.format("--no-wait ", "dpdk-lcore-mask='%s'" % lcore_mask) + max_idle = self.ovs_properties.get("max_idle", '') + if max_idle: + max_idle = ovs_other_config.format("", "max-idle=%s" % max_idle) + cmd_list = [ "mkdir -p /usr/local/var/run/openvswitch", "mkdir -p {}".format(os.path.dirname(log_path)), - "ovsdb-server --remote=punix:/{0}/{1} --pidfile --detach".format(vpath, - ovs_sock_path), + ("ovsdb-server --remote=punix:/{0}/{1} --remote=ptcp:6640" + " --pidfile --detach").format(vpath, ovs_sock_path), ovs_other_config.format("--no-wait ", "dpdk-init=true"), - ovs_other_config.format("--no-wait ", "dpdk-socket-mem='%s,%s'" % (socket0, socket1)), + ovs_other_config.format("--no-wait ", "dpdk-socket-mem='%d,%d'" % ( + self.get_dpdk_socket_mem_size(0), + self.get_dpdk_socket_mem_size(1))), lcore_mask, detach_cmd.format(vpath, ovs_sock_path, log_path), ovs_other_config.format("", "pmd-cpu-mask=%s" % pmd_mask), + max_idle, ] for cmd in cmd_list: @@ -163,56 +172,92 @@ class OvsDpdkContext(Context): def setup_ovs_bridge_add_flows(self): dpdk_args = "" - dpdk_list = [] vpath = self.ovs_properties.get("vpath", "/usr/local") version = self.ovs_properties.get('version', {}) ovs_ver = [int(x) for x in version.get('ovs', self.DEFAULT_OVS).split('.')] - ovs_add_port = \ - "ovs-vsctl add-port {br} {port} -- set Interface {port} type={type_}{dpdk_args}" - ovs_add_queue = "ovs-vsctl set Interface {port} options:n_rxq={queue}" - chmod_vpath = "chmod 0777 {0}/var/run/openvswitch/dpdkvhostuser*" - - cmd_dpdk_list = [ - "ovs-vsctl del-br br0", - "rm -rf {0}/var/run/openvswitch/dpdkvhostuser*".format(vpath), - "ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev", + ovs_add_port = ('ovs-vsctl add-port {br} {port} -- ' + 'set Interface {port} type={type_}{dpdk_args}' + '{dpdk_rxq}{pmd_rx_aff}') + chmod_vpath = 'chmod 0777 {0}/var/run/openvswitch/dpdkvhostuser*' + + cmd_list = [ + 'ovs-vsctl --if-exists del-br {0}'.format(MAIN_BRIDGE), + 'rm -rf {0}/var/run/openvswitch/dpdkvhostuser*'.format(vpath), + 'ovs-vsctl add-br {0} -- set bridge {0} datapath_type=netdev'. + format(MAIN_BRIDGE) ] + dpdk_rxq = "" + queues = self.ovs_properties.get("queues") + if queues: + dpdk_rxq = " options:n_rxq={queue}".format(queue=queues) - ordered_network = OrderedDict(self.networks) + # Sorting the array to make sure we execute dpdk0... in the order + ordered_network = collections.OrderedDict( + sorted(self.networks.items(), key=lambda t: t[1].get('port_num', 0))) + pmd_rx_aff_ports = self.ovs_properties.get("dpdk_pmd-rxq-affinity", {}) for index, vnf in enumerate(ordered_network.values()): if ovs_ver >= [2, 7, 0]: dpdk_args = " options:dpdk-devargs=%s" % vnf.get("phy_port") - dpdk_list.append(ovs_add_port.format(br='br0', port='dpdk%s' % vnf.get("port_num", 0), - type_='dpdk', dpdk_args=dpdk_args)) - dpdk_list.append(ovs_add_queue.format(port='dpdk%s' % vnf.get("port_num", 0), - queue=self.ovs_properties.get("queues", 1))) - - # Sorting the array to make sure we execute dpdk0... in the order - list.sort(dpdk_list) - cmd_dpdk_list.extend(dpdk_list) + affinity = pmd_rx_aff_ports.get(vnf.get("port_num", -1), "") + if affinity: + pmd_rx_aff = ' other_config:pmd-rxq-affinity=' \ + '"{affinity}"'.format(affinity=affinity) + else: + pmd_rx_aff = "" + cmd_list.append(ovs_add_port.format( + br=MAIN_BRIDGE, port='dpdk%s' % vnf.get("port_num", 0), + type_='dpdk', dpdk_args=dpdk_args, dpdk_rxq=dpdk_rxq, + pmd_rx_aff=pmd_rx_aff)) # Need to do two for loop to maintain the dpdk/vhost ports. + pmd_rx_aff_ports = self.ovs_properties.get("vhost_pmd-rxq-affinity", + {}) for index, _ in enumerate(ordered_network): - cmd_dpdk_list.append(ovs_add_port.format(br='br0', port='dpdkvhostuser%s' % index, - type_='dpdkvhostuser', dpdk_args="")) - - for cmd in cmd_dpdk_list: - LOG.info(cmd) - self.connection.execute(cmd) - - # Fixme: add flows code - ovs_flow = "ovs-ofctl add-flow br0 in_port=%s,action=output:%s" - + affinity = pmd_rx_aff_ports.get(index) + if affinity: + pmd_rx_aff = ' other_config:pmd-rxq-affinity=' \ + '"{affinity}"'.format(affinity=affinity) + else: + pmd_rx_aff = "" + cmd_list.append(ovs_add_port.format( + br=MAIN_BRIDGE, port='dpdkvhostuser%s' % index, + type_='dpdkvhostuser', dpdk_args="", dpdk_rxq=dpdk_rxq, + pmd_rx_aff=pmd_rx_aff)) + + ovs_flow = ("ovs-ofctl add-flow {0} in_port=%s,action=output:%s". + format(MAIN_BRIDGE)) network_count = len(ordered_network) + 1 for in_port, out_port in zip(range(1, network_count), range(network_count, network_count * 2)): - self.connection.execute(ovs_flow % (in_port, out_port)) - self.connection.execute(ovs_flow % (out_port, in_port)) + cmd_list.append(ovs_flow % (in_port, out_port)) + cmd_list.append(ovs_flow % (out_port, in_port)) + + cmd_list.append(chmod_vpath.format(vpath)) - self.connection.execute(chmod_vpath.format(vpath)) + for cmd in cmd_list: + LOG.info(cmd) + exit_status, _, stderr = self.connection.execute( + cmd, timeout=self.CMD_TIMEOUT) + if exit_status: + raise exceptions.OVSSetupError(command=cmd, error=stderr) + + def _check_hugepages(self): + meminfo = io.BytesIO() + self.connection.get_file_obj('/proc/meminfo', meminfo) + regex = re.compile(r"HugePages_Total:\s+(?P<hp_total>\d+)[\n\r]" + r"HugePages_Free:\s+(?P<hp_free>\d+)") + match = regex.search(meminfo.getvalue().decode('utf-8')) + if not match: + raise exceptions.OVSHugepagesInfoError() + if int(match.group('hp_total')) == 0: + raise exceptions.OVSHugepagesNotConfigured() + if int(match.group('hp_free')) == 0: + raise exceptions.OVSHugepagesZeroFree( + total_hugepages=int(match.group('hp_total'))) def cleanup_ovs_dpdk_env(self): - self.connection.execute("ovs-vsctl del-br br0") + self.connection.execute( + 'ovs-vsctl --if-exists del-br {0}'.format(MAIN_BRIDGE)) self.connection.execute("pkill -9 ovs") def check_ovs_dpdk_env(self): @@ -224,13 +269,15 @@ class OvsDpdkContext(Context): supported_version = self.SUPPORTED_OVS_TO_DPDK_MAP.get(ovs_ver, None) if supported_version is None or supported_version.split('.')[:2] != dpdk_ver[:2]: - raise Exception("Unsupported ovs '{}'. Please check the config...".format(ovs_ver)) + raise exceptions.OVSUnsupportedVersion( + ovs_version=ovs_ver, + ovs_to_dpdk_map=self.SUPPORTED_OVS_TO_DPDK_MAP) status = self.connection.execute("ovs-vsctl -V | grep -i '%s'" % ovs_ver)[0] if status: - deploy = OvsDeploy(self.connection, - get_nsb_option("bin_path"), - self.ovs_properties) + deploy = model.OvsDeploy(self.connection, + utils.get_nsb_option("bin_path"), + self.ovs_properties) deploy.ovs_deploy() def deploy(self): @@ -241,26 +288,21 @@ class OvsDpdkContext(Context): return self.connection = ssh.SSH.from_node(self.host_mgmt) - self.dpdk_nic_bind = provision_tool( - self.connection, - os.path.join(get_nsb_option("bin_path"), "dpdk-devbind.py")) # Check dpdk/ovs version, if not present install self.check_ovs_dpdk_env() # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. - StandaloneContextHelper.install_req_libs(self.connection) - self.networks = StandaloneContextHelper.get_nic_details(self.connection, - self.networks, - self.dpdk_nic_bind) + model.StandaloneContextHelper.install_req_libs(self.connection) + self.networks = model.StandaloneContextHelper.get_nic_details( + self.connection, self.networks, self.dpdk_devbind) self.setup_ovs() self.start_ovs_serverswitch() self.setup_ovs_bridge_add_flows() self.nodes = self.setup_ovs_dpdk_context() LOG.debug("Waiting for VM to come up...") - self.nodes = StandaloneContextHelper.wait_for_vnfs_to_start(self.connection, - self.servers, - self.nodes) + self.nodes = model.StandaloneContextHelper.wait_for_vnfs_to_start( + self.connection, self.servers, self.nodes) def undeploy(self): @@ -271,16 +313,31 @@ class OvsDpdkContext(Context): self.cleanup_ovs_dpdk_env() # Bind nics back to kernel - bind_cmd = "{dpdk_nic_bind} --force -b {driver} {port}" + bind_cmd = "{dpdk_devbind} --force -b {driver} {port}" for port in self.networks.values(): vpci = port.get("phy_port") phy_driver = port.get("driver") - self.connection.execute(bind_cmd.format(dpdk_nic_bind=self.dpdk_nic_bind, - driver=phy_driver, port=vpci)) + self.connection.execute(bind_cmd.format( + dpdk_devbind=self.dpdk_devbind, driver=phy_driver, port=vpci)) # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config. for vm in self.vm_names: - Libvirt.check_if_vm_exists_and_delete(vm, self.connection) + model.Libvirt.check_if_vm_exists_and_delete(vm, self.connection) + + def _get_physical_nodes(self): + return self.nfvi_host + + 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 == node_name] + if len(matching_nodes) == 0: + return None + + # self.nfvi_host always contain only one host + return "{}.{}".format(self.nfvi_host[0]["name"], self._name) def _get_server(self, attr_name): """lookup server info by name from context @@ -288,7 +345,7 @@ class OvsDpdkContext(Context): Keyword arguments: attr_name -- A name for a server listed in nodes config file """ - node_name, name = self.split_name(attr_name) + node_name, name = self.split_host_name(attr_name) if name is None or self.name != name: return None @@ -335,57 +392,78 @@ class OvsDpdkContext(Context): return result def configure_nics_for_ovs_dpdk(self): - portlist = OrderedDict(self.networks) + portlist = collections.OrderedDict(self.networks) for key in portlist: - mac = StandaloneContextHelper.get_mac_address() + mac = model.StandaloneContextHelper.get_mac_address() portlist[key].update({'mac': mac}) self.networks = portlist LOG.info("Ports %s", self.networks) - def _enable_interfaces(self, index, vfs, cfg): + def _enable_interfaces(self, index, vfs, xml_str): vpath = self.ovs_properties.get("vpath", "/usr/local") + queue = self.ovs_properties.get("queues", 1) vf = self.networks[vfs[0]] port_num = vf.get('port_num', 0) - vpci = PciAddress(vf['vpci'].strip()) + vpci = utils.PciAddress(vf['vpci'].strip()) # Generate the vpci for the interfaces slot = index + port_num + 10 vf['vpci'] = \ "{}:{}:{:02x}.{}".format(vpci.domain, vpci.bus, slot, vpci.function) - Libvirt.add_ovs_interface(vpath, port_num, vf['vpci'], vf['mac'], str(cfg)) + return model.Libvirt.add_ovs_interface( + vpath, port_num, vf['vpci'], vf['mac'], xml_str, queue) def setup_ovs_dpdk_context(self): nodes = [] self.configure_nics_for_ovs_dpdk() - for index, (key, vnf) in enumerate(OrderedDict(self.servers).items()): + hp_total_mb = int(self.vm_flavor.get('ram', '4096')) * len(self.servers) + common_utils.setup_hugepages(self.connection, (hp_total_mb + \ + self.get_dpdk_socket_mem_size(0) + \ + self.get_dpdk_socket_mem_size(1)) * 1024) + + self._check_hugepages() + + for index, (key, vnf) in enumerate(collections.OrderedDict( + self.servers).items()): cfg = '/tmp/vm_ovs_%d.xml' % index - vm_name = "vm_%d" % index + vm_name = "vm-%d" % index + cdrom_img = "/var/lib/libvirt/images/cdrom-%d.img" % index # 1. Check and delete VM if already exists - Libvirt.check_if_vm_exists_and_delete(vm_name, self.connection) + model.Libvirt.check_if_vm_exists_and_delete(vm_name, + self.connection) + xml_str, mac = model.Libvirt.build_vm_xml( + self.connection, self.vm_flavor, vm_name, index, cdrom_img) - _, mac = Libvirt.build_vm_xml(self.connection, self.vm_flavor, - cfg, vm_name, index) # 2: Cleanup already available VMs - for vkey, vfs in OrderedDict(vnf["network_ports"]).items(): - if vkey == "mgmt": - continue - self._enable_interfaces(index, vfs, cfg) + for vfs in [vfs for vfs_name, vfs in vnf["network_ports"].items() + if vfs_name != 'mgmt']: + xml_str = self._enable_interfaces(index, vfs, xml_str) # copy xml to target... + model.Libvirt.write_file(cfg, xml_str) self.connection.put(cfg, cfg) + node = self.vnf_node.generate_vnf_instance(self.vm_flavor, + self.networks, + self.host_mgmt.get('ip'), + key, vnf, mac) + # Generate public/private keys if password or private key file is not provided + node = model.StandaloneContextHelper.check_update_key(self.connection, + node, + vm_name, + self.name, + cdrom_img, + mac) + + # store vnf node details + nodes.append(node) + # NOTE: launch through libvirt LOG.info("virsh create ...") - Libvirt.virsh_create_vm(self.connection, cfg) + model.Libvirt.virsh_create_vm(self.connection, cfg) self.vm_names.append(vm_name) - # build vnf node details - nodes.append(self.vnf_node.generate_vnf_instance(self.vm_flavor, - self.networks, - self.host_mgmt.get('ip'), - key, vnf, mac)) - return nodes diff --git a/yardstick/benchmark/contexts/standalone/sriov.py b/yardstick/benchmark/contexts/standalone/sriov.py index 9d8423b5f..e037dd85a 100644 --- a/yardstick/benchmark/contexts/standalone/sriov.py +++ b/yardstick/benchmark/contexts/standalone/sriov.py @@ -16,49 +16,47 @@ from __future__ import absolute_import import os import logging import collections -from collections import OrderedDict from yardstick import ssh +from yardstick.benchmark import contexts +from yardstick.benchmark.contexts import base +from yardstick.benchmark.contexts.standalone import model +from yardstick.common import utils from yardstick.network_services.utils import get_nsb_option -from yardstick.network_services.utils import provision_tool -from yardstick.benchmark.contexts.base import Context -from yardstick.benchmark.contexts.standalone.model import Libvirt -from yardstick.benchmark.contexts.standalone.model import StandaloneContextHelper -from yardstick.benchmark.contexts.standalone.model import Server from yardstick.network_services.utils import PciAddress LOG = logging.getLogger(__name__) -class SriovContext(Context): +class SriovContext(base.Context): """ This class handles SRIOV standalone nodes - VM running on Non-Managed NFVi Configuration: sr-iov """ - __context_type__ = "StandaloneSriov" + __context_type__ = contexts.CONTEXT_STANDALONESRIOV def __init__(self): self.file_path = None self.sriov = [] self.first_run = True - self.dpdk_nic_bind = "" + self.dpdk_devbind = os.path.join(get_nsb_option('bin_path'), + 'dpdk-devbind.py') self.vm_names = [] - self.name = None self.nfvi_host = [] self.nodes = [] self.networks = {} self.attrs = {} self.vm_flavor = None self.servers = None - self.helper = StandaloneContextHelper() - self.vnf_node = Server() + self.helper = model.StandaloneContextHelper() + self.vnf_node = model.Server() self.drivers = [] super(SriovContext, self).__init__() def init(self, attrs): """initializes itself from the supplied arguments""" + super(SriovContext, self).init(attrs) - self.name = attrs["name"] self.file_path = attrs.get("file", "pod.yaml") self.nodes, self.nfvi_host, self.host_mgmt = \ @@ -83,21 +81,16 @@ class SriovContext(Context): return self.connection = ssh.SSH.from_node(self.host_mgmt) - self.dpdk_nic_bind = provision_tool( - self.connection, - os.path.join(get_nsb_option("bin_path"), "dpdk_nic_bind.py")) # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. - StandaloneContextHelper.install_req_libs(self.connection) - self.networks = StandaloneContextHelper.get_nic_details(self.connection, - self.networks, - self.dpdk_nic_bind) + model.StandaloneContextHelper.install_req_libs(self.connection) + self.networks = model.StandaloneContextHelper.get_nic_details( + self.connection, self.networks, self.dpdk_devbind) self.nodes = self.setup_sriov_context() LOG.debug("Waiting for VM to come up...") - self.nodes = StandaloneContextHelper.wait_for_vnfs_to_start(self.connection, - self.servers, - self.nodes) + self.nodes = model.StandaloneContextHelper.wait_for_vnfs_to_start( + self.connection, self.servers, self.nodes) def undeploy(self): """don't need to undeploy""" @@ -107,7 +100,7 @@ class SriovContext(Context): # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config. for vm in self.vm_names: - Libvirt.check_if_vm_exists_and_delete(vm, self.connection) + model.Libvirt.check_if_vm_exists_and_delete(vm, self.connection) # Bind nics back to kernel for ports in self.networks.values(): @@ -115,13 +108,29 @@ class SriovContext(Context): build_vfs = "echo 0 > /sys/bus/pci/devices/{0}/sriov_numvfs" self.connection.execute(build_vfs.format(ports.get('phy_port'))) + def _get_physical_nodes(self): + return self.nfvi_host + + def _get_physical_node_for_server(self, server_name): + + # self.nfvi_host always contain only one host. + 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 == node_name] + if len(matching_nodes) == 0: + return None + + return "{}.{}".format(self.nfvi_host[0]["name"], self._name) + def _get_server(self, attr_name): """lookup server info by name from context Keyword arguments: attr_name -- A name for a server listed in nodes config file """ - node_name, name = self.split_name(attr_name) + node_name, name = self.split_host_name(attr_name) if name is None or self.name != name: return None @@ -138,8 +147,8 @@ class SriovContext(Context): except StopIteration: pass else: - raise ValueError("Duplicate nodes!!! Nodes: %s %s", - (node, duplicate)) + raise ValueError("Duplicate nodes!!! Nodes: %s %s" + % (node, duplicate)) node["name"] = attr_name return node @@ -181,7 +190,7 @@ class SriovContext(Context): self.connection.execute(build_vfs.format(ports.get('phy_port'))) # configure VFs... - mac = StandaloneContextHelper.get_mac_address() + mac = model.StandaloneContextHelper.get_mac_address() interface = ports.get('interface') if interface is not None: self.connection.execute(vf_cmd.format(interface, mac)) @@ -203,10 +212,10 @@ class SriovContext(Context): slot = index + idx + 10 vf['vpci'] = \ "{}:{}:{:02x}.{}".format(vpci.domain, vpci.bus, slot, vpci.function) - Libvirt.add_sriov_interfaces( - vf['vpci'], vf['vf_pci']['vf_pci'], vf['mac'], str(cfg)) self.connection.execute("ifconfig %s up" % vf['interface']) self.connection.execute(vf_spoofchk.format(vf['interface'])) + return model.Libvirt.add_sriov_interfaces( + vf['vpci'], vf['vf_pci']['vf_pci'], vf['mac'], str(cfg)) def setup_sriov_context(self): nodes = [] @@ -214,35 +223,52 @@ class SriovContext(Context): # 1 : modprobe host_driver with num_vfs self.configure_nics_for_sriov() - for index, (key, vnf) in enumerate(OrderedDict(self.servers).items()): + hp_total_mb = int(self.vm_flavor.get('ram', '4096')) * len(self.servers) + utils.setup_hugepages(self.connection, hp_total_mb * 1024) + + for index, (key, vnf) in enumerate(collections.OrderedDict( + self.servers).items()): cfg = '/tmp/vm_sriov_%s.xml' % str(index) - vm_name = "vm_%s" % str(index) + vm_name = "vm-%s" % str(index) + cdrom_img = "/var/lib/libvirt/images/cdrom-%d.img" % index # 1. Check and delete VM if already exists - Libvirt.check_if_vm_exists_and_delete(vm_name, self.connection) + model.Libvirt.check_if_vm_exists_and_delete(vm_name, + self.connection) + xml_str, mac = model.Libvirt.build_vm_xml( + self.connection, self.vm_flavor, vm_name, index, cdrom_img) - _, mac = Libvirt.build_vm_xml(self.connection, self.vm_flavor, cfg, vm_name, index) # 2: Cleanup already available VMs - for idx, (vkey, vfs) in enumerate(OrderedDict(vnf["network_ports"]).items()): - if vkey == "mgmt": - continue - self._enable_interfaces(index, idx, vfs, cfg) + network_ports = collections.OrderedDict( + {k: v for k, v in vnf["network_ports"].items() if k != 'mgmt'}) + for idx, vfs in enumerate(network_ports.values()): + xml_str = self._enable_interfaces(index, idx, vfs, xml_str) # copy xml to target... + model.Libvirt.write_file(cfg, xml_str) self.connection.put(cfg, cfg) + node = self.vnf_node.generate_vnf_instance(self.vm_flavor, + self.networks, + self.host_mgmt.get('ip'), + key, vnf, mac) + # Generate public/private keys if password or private key file is not provided + node = model.StandaloneContextHelper.check_update_key(self.connection, + node, + vm_name, + self.name, + cdrom_img, + mac) + + # store vnf node details + nodes.append(node) + # NOTE: launch through libvirt LOG.info("virsh create ...") - Libvirt.virsh_create_vm(self.connection, cfg) + model.Libvirt.virsh_create_vm(self.connection, cfg) self.vm_names.append(vm_name) - # build vnf node details - nodes.append(self.vnf_node.generate_vnf_instance(self.vm_flavor, - self.networks, - self.host_mgmt.get('ip'), - key, vnf, mac)) - return nodes def _get_vf_data(self, value, vfmac, pfif): @@ -250,7 +276,8 @@ class SriovContext(Context): "mac": vfmac, "pf_if": pfif } - vfs = StandaloneContextHelper.get_virtual_devices(self.connection, value) + vfs = model.StandaloneContextHelper.get_virtual_devices( + self.connection, value) for k, v in vfs.items(): m = PciAddress(k.strip()) m1 = PciAddress(value.strip()) diff --git a/yardstick/benchmark/core/__init__.py b/yardstick/benchmark/core/__init__.py index 3e3aa99a1..3914e3237 100644 --- a/yardstick/benchmark/core/__init__.py +++ b/yardstick/benchmark/core/__init__.py @@ -23,6 +23,7 @@ class Param(object): self.task_args_file = kwargs.get('task-args-file') self.keep_deploy = kwargs.get('keep-deploy') self.parse_only = kwargs.get('parse-only') + self.render_only = kwargs.get('render-only') self.output_file = kwargs.get('output-file', '/tmp/yardstick.out') self.suite = kwargs.get('suite') self.task_id = kwargs.get('task_id') diff --git a/yardstick/benchmark/core/report.py b/yardstick/benchmark/core/report.py index 997a125e7..e5dc62050 100644 --- a/yardstick/benchmark/core/report.py +++ b/yardstick/benchmark/core/report.py @@ -1,7 +1,7 @@ -############################################################################# -# Copyright (c) 2017 Rajesh Kudaka +############################################################################## +# Copyright (c) 2017 Rajesh Kudaka <4k.rajesh@gmail.com> +# Copyright (c) 2018-2019 Intel Corporation. # -# Author: Rajesh Kudaka 4k.rajesh@gmail.com # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at @@ -10,33 +10,79 @@ """ Handler for yardstick command 'report' """ -from __future__ import print_function - -from __future__ import absolute_import - -import ast import re +import six import uuid +import jinja2 from api.utils import influx - -from django.conf import settings -from django.template import Context -from django.template import Template - -from oslo_utils import encodeutils from oslo_utils import uuidutils from yardstick.common import constants as consts -from yardstick.common.html_template import template from yardstick.common.utils import cliargs -settings.configure() + +class JSTree(object): + """Data structure to parse data for use with the JS library jsTree""" + def __init__(self): + self._created_nodes = ['#'] + self.jstree_data = [] + + def _create_node(self, _id): + """Helper method for format_for_jstree to create each node. + + Creates the node (and any required parents) and keeps track + of the created nodes. + + :param _id: (string) id of the node to be created + :return: None + """ + components = _id.split(".") + + if len(components) == 1: + text = components[0] + parent_id = "#" + else: + text = components[-1] + parent_id = ".".join(components[:-1]) + # make sure the parent has been created + if not parent_id in self._created_nodes: + self._create_node(parent_id) + + self.jstree_data.append({"id": _id, "text": text, "parent": parent_id}) + self._created_nodes.append(_id) + + def format_for_jstree(self, data): + """Format the data into the required format for jsTree. + + The data format expected is a list of metric names e.g.: + + ['tg__0.DropPackets', 'tg__0.LatencyAvg.5'] + + This data is converted into the format required for jsTree to group and + display the metrics in a hierarchial fashion, including creating a + number of parent nodes e.g.:: + + [{"id": "tg__0", "text": "tg__0", "parent": "#"}, + {"id": "tg__0.DropPackets", "text": "DropPackets", "parent": "tg__0"}, + {"id": "tg__0.LatencyAvg", "text": "LatencyAvg", "parent": "tg__0"}, + {"id": "tg__0.LatencyAvg.5", "text": "5", "parent": "tg__0.LatencyAvg"},] + + :param data: (list) data to be converted + :return: list + """ + self._created_nodes = ['#'] + self.jstree_data = [] + + for metric in data: + self._create_node(metric) + + return self.jstree_data class Report(object): """Report commands. - Set of commands to manage benchmark tasks. + Set of commands to manage reports. """ def __init__(self): @@ -45,7 +91,7 @@ class Report(object): self.task_id = "" def _validate(self, yaml_name, task_id): - if re.match("^[a-z0-9_-]+$", yaml_name): + if re.match(r"^[\w-]+$", yaml_name): self.yaml_name = yaml_name else: raise ValueError("invalid yaml_name", yaml_name) @@ -64,63 +110,280 @@ class Report(object): if query_exec: return query_exec else: - raise KeyError("Task ID or Test case not found..") + raise KeyError("Test case not found.") - def _get_tasks(self): - task_cmd = "select * from \"%s\" where task_id= '%s'" - task_query = task_cmd % (self.yaml_name, self.task_id) - query_exec = influx.query(task_query) + def _get_metrics(self): + metrics_cmd = "select * from \"%s\" where task_id = '%s'" + metrics_query = metrics_cmd % (self.yaml_name, self.task_id) + query_exec = influx.query(metrics_query) if query_exec: return query_exec else: - raise KeyError("Task ID or Test case not found..") + raise KeyError("Task ID or Test case not found.") + + def _get_task_start_time(self): + # The start time should come from the task or the metadata table. + # The first entry into influx for a task will be AFTER the first TC + # iteration + cmd = "select * from \"%s\" where task_id='%s' ORDER BY time ASC limit 1" + task_query = cmd % (self.yaml_name, self.task_id) + + query_exec = influx.query(task_query) + start_time = query_exec[0]['time'] + return start_time + + def _get_task_end_time(self): + # NOTE(elfoley): when using select first() and select last() for the + # DB query, the timestamp returned is 0, so later queries try to + # return metrics from 1970 + cmd = "select * from \"%s\" where task_id='%s' ORDER BY time DESC limit 1" + task_query = cmd % (self.yaml_name, self.task_id) + query_exec = influx.query(task_query) + end_time = query_exec[0]['time'] + return end_time + + def _get_baro_metrics(self): + start_time = self._get_task_start_time() + end_time = self._get_task_end_time() + metric_list = [ + "cpu_value", "cpufreq_value", "intel_pmu_value", + "virt_value", "memory_value"] + metrics = {} + times = [] + query_exec = {} + for metric in metric_list: + cmd = "select * from \"%s\" where time >= '%s' and time <= '%s'" + query = cmd % (metric, start_time, end_time) + query_exec[metric] = influx.query(query, db='collectd') + print("query_exec: {}".format(query_exec)) + + for metric in query_exec: + print("metric in query_exec: {}".format(metric)) + met_values = query_exec[metric] + print("met_values: {}".format(met_values)) + for x in met_values: + x['name'] = metric + metric_name = str('.'.join( + [x[f] for f in [ + 'host', 'name', 'type', 'type_instance', 'instance' + ] if x.get(f)])) + + if not metrics.get(metric_name): + metrics[metric_name] = {} + metric_time = self._get_trimmed_timestamp(x['time']) + times.append(metric_time) + time = metric_time + metrics[metric_name][time] = x['value'] + + times = sorted(list(set(times))) + + metrics['Timestamp'] = times + print("metrics: {}".format(metrics)) + return metrics + + def _get_trimmed_timestamp(self, metric_time, resolution=4): + if not isinstance(metric_time, str): + metric_time = metric_time.encode('utf8') # PY2: unicode to str + metric_time = metric_time[11:] # skip date, keep time + head, _, tail = metric_time.partition('.') # split HH:MM:SS & nsZ + metric_time = head + '.' + tail[:resolution] # join HH:MM:SS & .us + return metric_time + + def _get_timestamps(self, metrics, resolution=6): + # Extract the timestamps from a list of metrics + timestamps = [] + for metric in metrics: + metric_time = self._get_trimmed_timestamp( + metric['time'], resolution) + timestamps.append(metric_time) # HH:MM:SS.micros + return timestamps + + def _format_datasets(self, metric_name, metrics): + values = [] + for metric in metrics: + val = metric.get(metric_name, None) + if val is None: + # keep explicit None or missing entry as is + pass + elif isinstance(val, (int, float)): + # keep plain int or float as is + pass + elif six.PY2 and isinstance(val, + long): # pylint: disable=undefined-variable + # PY2: long value would be rendered with trailing L, + # which JS does not support, so convert it to float + val = float(val) + elif isinstance(val, six.string_types): + s = val + if not isinstance(s, str): + s = s.encode('utf8') # PY2: unicode to str + try: + # convert until failure + val = s + val = float(s) + val = int(s) + if six.PY2 and isinstance(val, + long): # pylint: disable=undefined-variable + val = float(val) # PY2: long to float + except ValueError: + # value may have been converted to a number + pass + finally: + # if val was not converted into a num, then it must be + # text, which shouldn't end up in the report + if isinstance(val, six.string_types): + val = None + else: + raise ValueError("Cannot convert %r" % val) + values.append(val) + return values @cliargs("task_id", type=str, help=" task id", nargs=1) @cliargs("yaml_name", type=str, help=" Yaml file Name", nargs=1) - def generate(self, args): - """Start report generation.""" + def _generate_common(self, args): + """Actions that are common to both report formats. + + Create the necessary data structure for rendering + the report templates. + """ self._validate(args.yaml_name[0], args.task_id[0]) - self.db_fieldkeys = self._get_fieldkeys() + db_fieldkeys = self._get_fieldkeys() + # list of dicts of: + # - PY2: unicode key and unicode value + # - PY3: str key and str value - self.db_task = self._get_tasks() + db_metrics = self._get_metrics() + # list of dicts of: + # - PY2: unicode key and { None | unicode | float | long | int } value + # - PY3: str key and { None | str | float | int } value - field_keys = [] - temp_series = [] - table_vals = {} + # extract fieldKey entries, and convert them to str where needed + field_keys = [key if isinstance(key, str) # PY3: already str + else key.encode('utf8') # PY2: unicode to str + for key in + [field['fieldKey'] + for field in db_fieldkeys]] - field_keys = [encodeutils.to_utf8(field['fieldKey']) - for field in self.db_fieldkeys] + # extract timestamps + self.Timestamp = self._get_timestamps(db_metrics) + # prepare return values + datasets = [] + table_vals = {'Timestamp': self.Timestamp} + + # extract and convert field values for key in field_keys: - self.Timestamp = [] - series = {} - values = [] - for task in self.db_task: - task_time = encodeutils.to_utf8(task['time']) - if not isinstance(task_time, str): - task_time = str(task_time, 'utf8') - key = str(key, 'utf8') - task_time = task_time[11:] - head, sep, tail = task_time.partition('.') - task_time = head + "." + tail[:6] - self.Timestamp.append(task_time) - if isinstance(task[key], float) is True: - values.append(task[key]) - else: - values.append(ast.literal_eval(task[key])) - table_vals['Timestamp'] = self.Timestamp + values = self._format_datasets(key, db_metrics) + datasets.append({'label': key, 'data': values}) table_vals[key] = values - series['name'] = key - series['data'] = values - temp_series.append(series) - - Template_html = Template(template) - Context_html = Context({"series": temp_series, - "Timestamp": self.Timestamp, - "task_id": self.task_id, - "table": table_vals}) + + return datasets, table_vals + + @cliargs("task_id", type=str, help=" task id", nargs=1) + @cliargs("yaml_name", type=str, help=" Yaml file Name", nargs=1) + def generate(self, args): + """Start report generation.""" + datasets, table_vals = self._generate_common(args) + + template_dir = consts.YARDSTICK_ROOT_PATH + "yardstick/common" + template_environment = jinja2.Environment( + autoescape=False, + loader=jinja2.FileSystemLoader(template_dir)) + + context = { + "datasets": datasets, + "Timestamps": self.Timestamp, + "task_id": self.task_id, + "table": table_vals, + } + + template_html = template_environment.get_template("report.html.j2") + + with open(consts.DEFAULT_HTML_FILE, "w") as file_open: + file_open.write(template_html.render(context)) + + print("Report generated. View %s" % consts.DEFAULT_HTML_FILE) + + def _combine_times(self, *args): + times = [] + # Combines an arbitrary number of lists + [times.extend(x) for x in args] + times = list(set(times)) + times.sort() + return times + + def _combine_metrics(self, *args): + baro_data, baro_time, yard_data, yard_time = args + combo_time = self._combine_times(baro_time, yard_time) + + data = {} + [data.update(x) for x in (baro_data, yard_data)] + + table_data = {} + table_data['Timestamp'] = combo_time + combo = {} + keys = sorted(data.keys()) + for met_name in data: + dataset = [] + for point in data[met_name]: + dataset.append({'x': point, 'y': data[met_name][point]}) + # the metrics need to be ordered by time + combo[met_name] = sorted(dataset, key=lambda i: i['x']) + for met_name in data: + table_data[met_name] = [] + for t in combo_time: + table_data[met_name].append(data[met_name].get(t, '')) + return combo, keys, table_data + + @cliargs("task_id", type=str, help=" task id", nargs=1) + @cliargs("yaml_name", type=str, help=" Yaml file Name", nargs=1) + def generate_nsb(self, args): + """Start NSB report generation.""" + _, report_data = self._generate_common(args) + report_time = report_data.pop('Timestamp') + report_meta = { + "testcase": self.yaml_name, + "task_id": self.task_id, + } + + yardstick_data = {} + for i, t in enumerate(report_time): + for m in report_data: + if not yardstick_data.get(m): + yardstick_data[m] = {} + yardstick_data[m][t] = report_data[m][i] + + baro_data = self._get_baro_metrics() + baro_timestamps = baro_data.pop('Timestamp') + + yard_timestamps = report_time + report_time = self._combine_times(yard_timestamps, baro_timestamps) + + combo_metrics, combo_keys, combo_table = self._combine_metrics( + baro_data, baro_timestamps, yardstick_data, yard_timestamps) + combo_time = self._combine_times(baro_timestamps, yard_timestamps) + combo_tree = JSTree().format_for_jstree(combo_keys) + + template_dir = consts.YARDSTICK_ROOT_PATH + "yardstick/common" + template_environment = jinja2.Environment( + autoescape=False, + loader=jinja2.FileSystemLoader(template_dir), + lstrip_blocks=True) + + combo_data = combo_metrics + context = { + "report_meta": report_meta, + "report_data": combo_data, + "report_time": combo_time, + "report_keys": combo_keys, + "report_tree": combo_tree, + "table_data": combo_table, + } + + template_html = template_environment.get_template("nsb_report.html.j2") + with open(consts.DEFAULT_HTML_FILE, "w") as file_open: - file_open.write(Template_html.render(Context_html)) + file_open.write(template_html.render(context)) - print("Report generated. View /tmp/yardstick.htm") + print("Report generated. View %s" % consts.DEFAULT_HTML_FILE) diff --git a/yardstick/benchmark/core/task.py b/yardstick/benchmark/core/task.py index 9b1b3f851..bcca3558f 100644 --- a/yardstick/benchmark/core/task.py +++ b/yardstick/benchmark/core/task.py @@ -7,14 +7,11 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -""" Handler for yardstick command 'task' """ - -from __future__ import absolute_import -from __future__ import print_function import sys import os from collections import OrderedDict +import six import yaml import atexit import ipaddress @@ -26,14 +23,16 @@ import collections from six.moves import filter from jinja2 import Environment -from yardstick.benchmark.contexts.base import Context +from yardstick.benchmark import contexts +from yardstick.benchmark.contexts import base as base_context from yardstick.benchmark.runners import base as base_runner from yardstick.common.constants import CONF_FILE from yardstick.common.yaml_loader import yaml_load from yardstick.dispatcher.base import Base as DispatcherBase -from yardstick.common.task_template import TaskTemplate -from yardstick.common import utils from yardstick.common import constants +from yardstick.common import exceptions as y_exc +from yardstick.common import task_template +from yardstick.common import utils from yardstick.common.html_template import report_template output_file_default = "/tmp/yardstick.out" @@ -57,7 +56,7 @@ class Task(object): # pragma: no cover out_types = [s.strip() for s in dispatchers.split(',')] output_config['DEFAULT']['dispatcher'] = out_types - def start(self, args, **kwargs): + def start(self, args, **kwargs): # pylint: disable=unused-argument """Start a benchmark scenario.""" atexit.register(self.atexit_handler) @@ -69,7 +68,7 @@ class Task(object): # pragma: no cover try: output_config = utils.parse_ini_file(CONF_FILE) - except Exception: + except Exception: # pylint: disable=broad-except # all error will be ignore, the default value is {} output_config = {} @@ -89,8 +88,7 @@ class Task(object): # pragma: no cover if args.suite: # 1.parse suite, return suite_params info - task_files, task_args, task_args_fnames = \ - parser.parse_suite() + task_files, task_args, task_args_fnames = parser.parse_suite() else: task_files = [parser.path] task_args = [args.task_args] @@ -103,32 +101,39 @@ class Task(object): # pragma: no cover sys.exit(0) testcases = {} - # parse task_files - for i in range(0, len(task_files)): - one_task_start_time = time.time() - parser.path = task_files[i] - scenarios, run_in_parallel, meet_precondition, contexts = \ - parser.parse_task(self.task_id, task_args[i], - task_args_fnames[i]) - - self.contexts.extend(contexts) + tasks = self._parse_tasks(parser, task_files, args, task_args, + task_args_fnames) - if not meet_precondition: - LOG.info("meet_precondition is %s, please check envrionment", - meet_precondition) + # Execute task files. + for i, _ in enumerate(task_files): + one_task_start_time = time.time() + self.contexts.extend(tasks[i]['contexts']) + if not tasks[i]['meet_precondition']: + LOG.info('"meet_precondition" is %s, please check environment', + tasks[i]['meet_precondition']) continue - case_name = os.path.splitext(os.path.basename(task_files[i]))[0] try: - data = self._run(scenarios, run_in_parallel, args.output_file) + success, data = self._run(tasks[i]['scenarios'], + tasks[i]['run_in_parallel'], + output_config) except KeyboardInterrupt: raise - except Exception: - LOG.error('Testcase: "%s" FAILED!!!', case_name, exc_info=True) - testcases[case_name] = {'criteria': 'FAIL', 'tc_data': []} + except Exception: # pylint: disable=broad-except + LOG.error('Testcase: "%s" FAILED!!!', tasks[i]['case_name'], + exc_info=True) + testcases[tasks[i]['case_name']] = {'criteria': 'FAIL', + 'tc_data': []} else: - LOG.info('Testcase: "%s" SUCCESS!!!', case_name) - testcases[case_name] = {'criteria': 'PASS', 'tc_data': data} + if success: + LOG.info('Testcase: "%s" SUCCESS!!!', tasks[i]['case_name']) + testcases[tasks[i]['case_name']] = {'criteria': 'PASS', + 'tc_data': data} + else: + LOG.error('Testcase: "%s" FAILED!!!', tasks[i]['case_name'], + exc_info=True) + testcases[tasks[i]['case_name']] = {'criteria': 'FAIL', + 'tc_data': data} if args.keep_deploy: # keep deployment, forget about stack @@ -151,9 +156,8 @@ class Task(object): # pragma: no cover LOG.info("Total finished in %d secs", total_end_time - total_start_time) - scenario = scenarios[0] - LOG.info("To generate report, execute command " - "'yardstick report generate %(task_id)s %(tc)s'", scenario) + LOG.info('To generate report, execute command "yardstick report ' + 'generate %s <YAML_NAME>"', self.task_id) LOG.info("Task ALL DONE, exiting") return result @@ -232,51 +236,53 @@ class Task(object): # pragma: no cover def _do_output(self, output_config, result): dispatchers = DispatcherBase.get(output_config) + dispatchers = (d for d in dispatchers if d.__dispatcher_type__ != 'Influxdb') for dispatcher in dispatchers: dispatcher.flush_result_data(result) - def _run(self, scenarios, run_in_parallel, output_file): + def _run(self, scenarios, run_in_parallel, output_config): """Deploys context and calls runners""" for context in self.contexts: context.deploy() background_runners = [] + task_success = True result = [] # Start all background scenarios for scenario in filter(_is_background_scenario, scenarios): scenario["runner"] = dict(type="Duration", duration=1000000000) - runner = self.run_one_scenario(scenario, output_file) + runner = self.run_one_scenario(scenario, output_config) background_runners.append(runner) runners = [] if run_in_parallel: for scenario in scenarios: if not _is_background_scenario(scenario): - runner = self.run_one_scenario(scenario, output_file) + runner = self.run_one_scenario(scenario, output_config) runners.append(runner) # Wait for runners to finish for runner in runners: status = runner_join(runner, background_runners, self.outputs, result) if status != 0: - raise RuntimeError( - "{0} runner status {1}".format(runner.__execution_type__, status)) - LOG.info("Runner ended, output in %s", output_file) + LOG.error("%s runner status %s", runner.__execution_type__, status) + task_success = False + LOG.info("Runner ended") else: # run serially for scenario in scenarios: if not _is_background_scenario(scenario): - runner = self.run_one_scenario(scenario, output_file) + runner = self.run_one_scenario(scenario, output_config) status = runner_join(runner, background_runners, self.outputs, result) if status != 0: LOG.error('Scenario NO.%s: "%s" ERROR!', scenarios.index(scenario) + 1, scenario.get('type')) - raise RuntimeError( - "{0} runner status {1}".format(runner.__execution_type__, status)) - LOG.info("Runner ended, output in %s", output_file) + LOG.error("%s runner status %s", runner.__execution_type__, status) + task_success = False + LOG.info("Runner ended") # Abort background runners for runner in background_runners: @@ -292,7 +298,7 @@ class Task(object): # pragma: no cover base_runner.Runner.release(runner) print("Background task ended") - return result + return task_success, result def atexit_handler(self): """handler for process termination""" @@ -308,29 +314,54 @@ class Task(object): # pragma: no cover return {k: self._parse_options(v) for k, v in op.items()} elif isinstance(op, list): return [self._parse_options(v) for v in op] - elif isinstance(op, str): + elif isinstance(op, six.string_types): return self.outputs.get(op[1:]) if op.startswith('$') else op else: return op - def run_one_scenario(self, scenario_cfg, output_file): + def _parse_tasks(self, parser, task_files, args, task_args, + task_args_fnames): + tasks = [] + + # Parse task_files. + for i, _ in enumerate(task_files): + parser.path = task_files[i] + tasks.append(parser.parse_task(self.task_id, task_args[i], + task_args_fnames[i])) + tasks[i]['case_name'] = os.path.splitext( + os.path.basename(task_files[i]))[0] + + if args.render_only: + utils.makedirs(args.render_only) + for idx, task in enumerate(tasks): + output_file_name = os.path.abspath(os.path.join( + args.render_only, + '{0:03d}-{1}.yml'.format(idx, task['case_name']))) + utils.write_file(output_file_name, task['rendered']) + + sys.exit(0) + + return tasks + + def run_one_scenario(self, scenario_cfg, output_config): """run one scenario using context""" runner_cfg = scenario_cfg["runner"] - runner_cfg['output_filename'] = output_file + runner_cfg['output_config'] = output_config options = scenario_cfg.get('options', {}) scenario_cfg['options'] = self._parse_options(options) # TODO support get multi hosts/vms info context_cfg = {} - server_name = scenario_cfg.get('options', {}).get('server_name', {}) + options = scenario_cfg.get('options') or {} + server_name = options.get('server_name') or {} def config_context_target(cfg): target = cfg['target'] if is_ip_addr(target): context_cfg['target'] = {"ipaddr": target} else: - context_cfg['target'] = Context.get_server(target) + context_cfg['target'] = base_context.Context.get_server(target) if self._is_same_context(cfg["host"], target): context_cfg['target']["ipaddr"] = context_cfg['target']["private_ip"] else: @@ -338,13 +369,13 @@ class Task(object): # pragma: no cover host_name = server_name.get('host', scenario_cfg.get('host')) if host_name: - context_cfg['host'] = Context.get_server(host_name) + context_cfg['host'] = base_context.Context.get_server(host_name) for item in [server_name, scenario_cfg]: try: config_context_target(item) except KeyError: - pass + LOG.debug("Got a KeyError in config_context_target(%s)", item) else: break @@ -355,7 +386,8 @@ class Task(object): # pragma: no cover ip_list.append(target) context_cfg['target'] = {} else: - context_cfg['target'] = Context.get_server(target) + context_cfg['target'] = ( + base_context.Context.get_server(target)) if self._is_same_context(scenario_cfg["host"], target): ip_list.append(context_cfg["target"]["private_ip"]) @@ -383,7 +415,8 @@ class Task(object): # pragma: no cover with attribute name mapping when using external heat templates """ for context in self.contexts: - if context.__context_type__ not in {"Heat", "Kubernetes"}: + if context.__context_type__ not in {contexts.CONTEXT_HEAT, + contexts.CONTEXT_KUBERNETES}: continue host = context._get_server(host_attr) @@ -478,33 +511,42 @@ class TaskParser(object): # pragma: no cover return valid_task_files, valid_task_args, valid_task_args_fnames - def parse_task(self, task_id, task_args=None, task_args_file=None): - """parses the task file and return an context and scenario instances""" - LOG.info("Parsing task config: %s", self.path) + def _render_task(self, task_args, task_args_file): + """Render the input task with the given arguments + :param task_args: (dict) arguments to render the task + :param task_args_file: (str) file containing the arguments to render + the task + :return: (str) task file rendered + """ try: kw = {} if task_args_file: with open(task_args_file) as f: - kw.update(parse_task_args("task_args_file", f.read())) - kw.update(parse_task_args("task_args", task_args)) + kw.update(parse_task_args('task_args_file', f.read())) + kw.update(parse_task_args('task_args', task_args)) except TypeError: - raise TypeError() + raise y_exc.TaskRenderArgumentError() + input_task = None try: with open(self.path) as f: - try: - input_task = f.read() - rendered_task = TaskTemplate.render(input_task, **kw) - except Exception as e: - LOG.exception('Failed to render template:\n%s\n', input_task) - raise e - LOG.debug("Input task is:\n%s\n", rendered_task) - - cfg = yaml_load(rendered_task) - except IOError as ioerror: - sys.exit(ioerror) + input_task = f.read() + rendered_task = task_template.TaskTemplate.render(input_task, **kw) + LOG.debug('Input task is:\n%s', rendered_task) + parsed_task = yaml_load(rendered_task) + except (IOError, OSError): + raise y_exc.TaskReadError(task_file=self.path) + except Exception: + raise y_exc.TaskRenderError(input_task=input_task) + + return parsed_task, rendered_task + def parse_task(self, task_id, task_args=None, task_args_file=None): + """parses the task file and return an context and scenario instances""" + LOG.info("Parsing task config: %s", self.path) + + cfg, rendered = self._render_task(task_args, task_args_file) self._check_schema(cfg["schema"], "task") meet_precondition = self._check_precondition(cfg) @@ -515,21 +557,19 @@ class TaskParser(object): # pragma: no cover elif "contexts" in cfg: context_cfgs = cfg["contexts"] else: - context_cfgs = [{"type": "Dummy"}] + context_cfgs = [{"type": contexts.CONTEXT_DUMMY}] - contexts = [] - name_suffix = '-{}'.format(task_id[:8]) + _contexts = [] for cfg_attrs in context_cfgs: - try: - cfg_attrs['name'] = '{}{}'.format(cfg_attrs['name'], - name_suffix) - except KeyError: - pass + + cfg_attrs['task_id'] = task_id # default to Heat context because we are testing OpenStack - context_type = cfg_attrs.get("type", "Heat") - context = Context.get(context_type) + context_type = cfg_attrs.get("type", contexts.CONTEXT_HEAT) + context = base_context.Context.get(context_type) context.init(cfg_attrs) - contexts.append(context) + # Update the name in case the context has used the name_suffix + cfg_attrs['name'] = context.name + _contexts.append(context) run_in_parallel = cfg.get("run_in_parallel", False) @@ -542,16 +582,98 @@ class TaskParser(object): # pragma: no cover # relative to task path scenario["task_path"] = os.path.dirname(self.path) - change_server_name(scenario, name_suffix) - - try: - for node in scenario['nodes']: - scenario['nodes'][node] += name_suffix - except KeyError: - pass + self._change_node_names(scenario, _contexts) # TODO we need something better here, a class that represent the file - return cfg["scenarios"], run_in_parallel, meet_precondition, contexts + return {'scenarios': cfg['scenarios'], + 'run_in_parallel': run_in_parallel, + 'meet_precondition': meet_precondition, + 'contexts': _contexts, + 'rendered': rendered} + + @staticmethod + def _change_node_names(scenario, _contexts): + """Change the node names in a scenario, depending on the context config + + The nodes (VMs or physical servers) are referred in the context section + with the name of the server and the name of the context: + <server name>.<context name> + + If the context is going to be undeployed at the end of the test, the + task ID is suffixed to the name to avoid interferences with previous + deployments. If the context needs to be deployed at the end of the + test, the name assigned is kept. + + There are several places where a node name could appear in the scenario + configuration: + scenario: + host: athena.demo + target: kratos.demo + targets: + - athena.demo + - kratos.demo + + scenario: + options: + server_name: # JIRA: YARDSTICK-810 + host: athena.demo + target: kratos.demo + + scenario: + nodes: + tg__0: trafficgen_0.yardstick + vnf__0: vnf_0.yardstick + + scenario: + nodes: + tg__0: + name: trafficgen_0.yardstick + public_ip_attr: "server1_public_ip" + private_ip_attr: "server1_private_ip" + vnf__0: + name: vnf_0.yardstick + public_ip_attr: "server2_public_ip" + private_ip_attr: "server2_private_ip" + NOTE: in Kubernetes context, the separator character between the server + name and the context name is "-": + scenario: + host: host-k8s + target: target-k8s + """ + def qualified_name(name): + for context in _contexts: + host_name, ctx_name = context.split_host_name(name) + if context.assigned_name == ctx_name: + return '{}{}{}'.format(host_name, + context.host_name_separator, + context.name) + + raise y_exc.ScenarioConfigContextNameNotFound(host_name=name) + + if 'host' in scenario: + scenario['host'] = qualified_name(scenario['host']) + if 'target' in scenario: + scenario['target'] = qualified_name(scenario['target']) + options = scenario.get('options') or {} + server_name = options.get('server_name') or {} + if 'host' in server_name: + server_name['host'] = qualified_name(server_name['host']) + if 'target' in server_name: + server_name['target'] = qualified_name(server_name['target']) + if 'targets' in scenario: + for idx, target in enumerate(scenario['targets']): + scenario['targets'][idx] = qualified_name(target) + if 'nodes' in scenario: + for scenario_node, target in scenario['nodes'].items(): + if isinstance(target, collections.Mapping): + # Update node info on scenario with context info + # Just update the node name with context + # Append context information + target['name'] = qualified_name(target['name']) + # Then update node + scenario['nodes'][scenario_node] = target + else: + scenario['nodes'][scenario_node] = qualified_name(target) def _check_schema(self, cfg_schema, schema_type): """Check if config file is using the correct schema type""" @@ -618,7 +740,8 @@ def _is_background_scenario(scenario): def parse_nodes_with_context(scenario_cfg): """parse the 'nodes' fields in scenario """ # ensure consistency in node instantiation order - return OrderedDict((nodename, Context.get_server(scenario_cfg["nodes"][nodename])) + return OrderedDict((nodename, base_context.Context.get_server( + scenario_cfg["nodes"][nodename])) for nodename in sorted(scenario_cfg["nodes"])) @@ -634,7 +757,7 @@ def get_networks_from_nodes(nodes): network_name = interface.get('network_name') if not network_name: continue - network = Context.get_network(network_name) + network = base_context.Context.get_network(network_name) if network: networks[network['name']] = network return networks @@ -685,30 +808,3 @@ def parse_task_args(src_name, args): % {"src": src_name, "src_type": type(kw)}) raise TypeError() return kw - - -def change_server_name(scenario, suffix): - - def add_suffix(cfg, key): - try: - value = cfg[key] - except KeyError: - pass - else: - try: - value['name'] += suffix - except TypeError: - cfg[key] += suffix - - server_name = scenario.get('options', {}).get('server_name', {}) - - add_suffix(scenario, 'host') - add_suffix(scenario, 'target') - add_suffix(server_name, 'host') - add_suffix(server_name, 'target') - - try: - key = 'targets' - scenario[key] = ['{}{}'.format(a, suffix) for a in scenario[key]] - except KeyError: - pass diff --git a/yardstick/benchmark/runners/arithmetic.py b/yardstick/benchmark/runners/arithmetic.py index 6aaaed888..ecb59f960 100755 --- a/yardstick/benchmark/runners/arithmetic.py +++ b/yardstick/benchmark/runners/arithmetic.py @@ -37,6 +37,7 @@ import six from six.moves import range from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -86,7 +87,7 @@ def _worker_process(queue, cls, method_name, scenario_cfg, loop_iter = six.moves.zip(*param_iters) else: LOG.warning("iter_type unrecognized: %s", iter_type) - raise TypeError("iter_type unrecognized: %s", iter_type) + raise TypeError("iter_type unrecognized: %s" % iter_type) # Populate options and run the requested method for each value combination for comb_values in loop_iter: @@ -105,14 +106,14 @@ def _worker_process(queue, cls, method_name, scenario_cfg, try: result = method(data) - except AssertionError as assertion: + except y_exc.SLAValidationError as error: # SLA validation failed in scenario, determine what to do now if sla_action == "assert": raise elif sla_action == "monitor": - LOG.warning("SLA validation failed: %s", assertion.args) - errors = assertion.args - except Exception as e: + LOG.warning("SLA validation failed: %s", error.args) + errors = error.args + except Exception as e: # pylint: disable=broad-except errors = traceback.format_exc() LOG.exception(e) else: diff --git a/yardstick/benchmark/runners/base.py b/yardstick/benchmark/runners/base.py index a887fa5b3..94de45d1e 100755 --- a/yardstick/benchmark/runners/base.py +++ b/yardstick/benchmark/runners/base.py @@ -12,24 +12,22 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +# +# This is a modified copy of ``rally/rally/benchmark/runners/base.py`` -# yardstick comment: this is a modified copy of -# rally/rally/benchmark/runners/base.py - -from __future__ import absolute_import - +import importlib import logging import multiprocessing import subprocess import time import traceback -import importlib - -from six.moves.queue import Empty +from six import moves -import yardstick.common.utils as utils from yardstick.benchmark.scenarios import base as base_scenario +from yardstick.common import utils +from yardstick.dispatcher.base import Base as DispatcherBase + log = logging.getLogger(__name__) @@ -39,7 +37,7 @@ def _execute_shell_command(command): exitcode = 0 try: output = subprocess.check_output(command, shell=True) - except Exception: + except subprocess.CalledProcessError: exitcode = -1 output = traceback.format_exc() log.error("exec command '%s' error:\n ", command) @@ -79,6 +77,33 @@ def _periodic_action(interval, command, queue): queue.put({'periodic-action-data': data}) +class ScenarioOutput(dict): + + QUEUE_PUT_TIMEOUT = 10 + + def __init__(self, queue, **kwargs): + super(ScenarioOutput, self).__init__() + self._queue = queue + self.result_ext = dict() + for key, val in kwargs.items(): + self.result_ext[key] = val + setattr(self, key, val) + + def push(self, data=None, add_timestamp=True): + if data is None: + data = dict(self) + + if add_timestamp: + result = {'timestamp': time.time(), 'data': data} + else: + result = data + + for key in self.result_ext.keys(): + result[key] = getattr(self, key) + + self._queue.put(result, True, self.QUEUE_PUT_TIMEOUT) + + class Runner(object): runners = [] @@ -119,7 +144,7 @@ class Runner(object): @staticmethod def terminate_all(): """Terminate all runners (subprocesses)""" - log.debug("Terminating all runners", exc_info=True) + log.debug("Terminating all runners") # release dumper process as some errors before any runner is created if not Runner.runners: @@ -137,6 +162,8 @@ class Runner(object): Runner.release(runner) def __init__(self, config): + self.task_id = None + self.case_name = None self.config = config self.periodic_action_process = None self.output_queue = multiprocessing.Queue() @@ -170,6 +197,8 @@ class Runner(object): cls = getattr(module, path_split[-1]) self.config['object'] = class_name + self.case_name = scenario_cfg['tc'] + self.task_id = scenario_cfg['task_id'] self.aborted.clear() # run a potentially configured pre-start action @@ -239,16 +268,30 @@ class Runner(object): log.debug("output_queue size %s", self.output_queue.qsize()) try: result.update(self.output_queue.get(True, 1)) - except Empty: + except moves.queue.Empty: pass return result def get_result(self): result = [] + + dispatcher = self.config['output_config']['DEFAULT']['dispatcher'] + output_in_influxdb = 'influxdb' in dispatcher + while not self.result_queue.empty(): log.debug("result_queue size %s", self.result_queue.qsize()) try: - result.append(self.result_queue.get(True, 1)) - except Empty: + one_record = self.result_queue.get(True, 1) + except moves.queue.Empty: pass + else: + if output_in_influxdb: + self._output_to_influxdb(one_record) + + result.append(one_record) return result + + def _output_to_influxdb(self, record): + dispatchers = DispatcherBase.get(self.config['output_config']) + dispatcher = next((d for d in dispatchers if d.__dispatcher_type__ == 'Influxdb')) + dispatcher.upload_one_record(record, self.case_name, '', task_id=self.task_id) diff --git a/yardstick/benchmark/runners/duration.py b/yardstick/benchmark/runners/duration.py index fbf72a74c..55c3690fd 100644 --- a/yardstick/benchmark/runners/duration.py +++ b/yardstick/benchmark/runners/duration.py @@ -27,6 +27,7 @@ import traceback import time from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -66,18 +67,21 @@ def _worker_process(queue, cls, method_name, scenario_cfg, data = {} errors = "" + benchmark.pre_run_wait_time(interval) + try: result = method(data) - except AssertionError as assertion: + except y_exc.SLAValidationError as error: # SLA validation failed in scenario, determine what to do now if sla_action == "assert": + benchmark.teardown() raise elif sla_action == "monitor": - LOG.warning("SLA validation failed: %s", assertion.args) - errors = assertion.args + LOG.warning("SLA validation failed: %s", error.args) + errors = error.args # catch all exceptions because with multiprocessing we can have un-picklable exception # problems https://bugs.python.org/issue9400 - except Exception: + except Exception: # pylint: disable=broad-except errors = traceback.format_exc() LOG.exception("") else: @@ -86,7 +90,7 @@ def _worker_process(queue, cls, method_name, scenario_cfg, # if we do timeout we don't care about dropping individual KPIs output_queue.put(result, True, QUEUE_PUT_TIMEOUT) - time.sleep(interval) + benchmark.post_run_wait_time(interval) benchmark_output = { 'timestamp': time.time(), @@ -102,7 +106,8 @@ def _worker_process(queue, cls, method_name, scenario_cfg, sequence += 1 - if (errors and sla_action is None) or time.time() > timeout or aborted.is_set(): + if ((errors and sla_action is None) or time.time() > timeout + or aborted.is_set() or benchmark.is_ended()): LOG.info("Worker END") break diff --git a/yardstick/benchmark/runners/dynamictp.py b/yardstick/benchmark/runners/dynamictp.py index 63bfc823a..88d3c5704 100755 --- a/yardstick/benchmark/runners/dynamictp.py +++ b/yardstick/benchmark/runners/dynamictp.py @@ -27,6 +27,7 @@ import traceback import os from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -80,10 +81,10 @@ def _worker_process(queue, cls, method_name, scenario_cfg, try: method(data) - except AssertionError as assertion: - LOG.warning("SLA validation failed: %s" % assertion.args) + except y_exc.SLAValidationError as error: + LOG.warning("SLA validation failed: %s", error.args) too_high = True - except Exception as e: + except Exception as e: # pylint: disable=broad-except errors = traceback.format_exc() LOG.exception(e) diff --git a/yardstick/benchmark/runners/iteration.py b/yardstick/benchmark/runners/iteration.py index cb0424377..15dad2cd5 100644 --- a/yardstick/benchmark/runners/iteration.py +++ b/yardstick/benchmark/runners/iteration.py @@ -23,12 +23,12 @@ from __future__ import absolute_import import logging import multiprocessing -import time import traceback import os from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -39,8 +39,6 @@ QUEUE_PUT_TIMEOUT = 10 def _worker_process(queue, cls, method_name, scenario_cfg, context_cfg, aborted, output_queue): - sequence = 1 - runner_cfg = scenario_cfg['runner'] interval = runner_cfg.get("interval", 1) @@ -52,6 +50,7 @@ def _worker_process(queue, cls, method_name, scenario_cfg, runner_cfg['runner_id'] = os.getpid() + scenario_output = base.ScenarioOutput(queue, sequence=1, errors="") benchmark = cls(scenario_cfg, context_cfg) if "setup" in run_step: benchmark.setup() @@ -66,20 +65,21 @@ def _worker_process(queue, cls, method_name, scenario_cfg, LOG.debug("runner=%(runner)s seq=%(sequence)s START", {"runner": runner_cfg["runner_id"], - "sequence": sequence}) + "sequence": scenario_output.sequence}) - data = {} - errors = "" + scenario_output.clear() + scenario_output.errors = "" + benchmark.pre_run_wait_time(interval) try: - result = method(data) - except AssertionError as assertion: + result = method(scenario_output) + except y_exc.SLAValidationError as error: # SLA validation failed in scenario, determine what to do now if sla_action == "assert": raise elif sla_action == "monitor": - LOG.warning("SLA validation failed: %s", assertion.args) - errors = assertion.args + LOG.warning("SLA validation failed: %s", error.args) + scenario_output.errors = error.args elif sla_action == "rate-control": try: scenario_cfg['options']['rate'] @@ -88,36 +88,31 @@ def _worker_process(queue, cls, method_name, scenario_cfg, scenario_cfg['options']['rate'] = 100 scenario_cfg['options']['rate'] -= delta - sequence = 1 + scenario_output.sequence = 1 continue - except Exception: - errors = traceback.format_exc() + except Exception: # pylint: disable=broad-except + scenario_output.errors = traceback.format_exc() LOG.exception("") + raise else: if result: # add timeout for put so we don't block test # if we do timeout we don't care about dropping individual KPIs output_queue.put(result, True, QUEUE_PUT_TIMEOUT) - time.sleep(interval) - - benchmark_output = { - 'timestamp': time.time(), - 'sequence': sequence, - 'data': data, - 'errors': errors - } + benchmark.post_run_wait_time(interval) - queue.put(benchmark_output, True, QUEUE_PUT_TIMEOUT) + if scenario_output: + scenario_output.push() LOG.debug("runner=%(runner)s seq=%(sequence)s END", {"runner": runner_cfg["runner_id"], - "sequence": sequence}) + "sequence": scenario_output.sequence}) - sequence += 1 + scenario_output.sequence += 1 - if (errors and sla_action is None) or \ - (sequence > iterations or aborted.is_set()): + if (scenario_output.errors and sla_action is None) or \ + (scenario_output.sequence > iterations or aborted.is_set()): LOG.info("worker END") break if "teardown" in run_step: diff --git a/yardstick/benchmark/runners/proxduration.py b/yardstick/benchmark/runners/proxduration.py new file mode 100644 index 000000000..e217904b9 --- /dev/null +++ b/yardstick/benchmark/runners/proxduration.py @@ -0,0 +1,166 @@ +# Copyright 2014: Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# yardstick comment: this is a modified copy of +# rally/rally/benchmark/runners/constant.py + +"""A runner that runs a specific time before it returns +""" + +from __future__ import absolute_import + +import os +import multiprocessing +import logging +import traceback +import time + +from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc +from yardstick.common import constants + +LOG = logging.getLogger(__name__) + +def _worker_process(queue, cls, method_name, scenario_cfg, + context_cfg, aborted, output_queue): + + sequence = 1 + + runner_cfg = scenario_cfg['runner'] + + requested_interval = interval = runner_cfg.get("interval", 1) + duration = runner_cfg.get("duration", 60) + sampled = runner_cfg.get("sampled", False) + + LOG.info("Worker START, duration is %ds", duration) + LOG.debug("class is %s", cls) + + runner_cfg['runner_id'] = os.getpid() + + benchmark = cls(scenario_cfg, context_cfg) + benchmark.setup() + method = getattr(benchmark, method_name) + + sla_action = None + if "sla" in scenario_cfg: + sla_action = scenario_cfg["sla"].get("action", "assert") + + + start = time.time() + timeout = start + duration + while True: + + LOG.debug("runner=%(runner)s seq=%(sequence)s START", + {"runner": runner_cfg["runner_id"], "sequence": sequence}) + + data = {} + errors = "" + + benchmark.pre_run_wait_time(interval) + + if sampled: + try: + pre_adjustment = time.time() + result = method(data) + post_adjustment = time.time() + if requested_interval > post_adjustment - pre_adjustment: + interval = requested_interval - (post_adjustment - pre_adjustment) + else: + interval = 0 + + except y_exc.SLAValidationError as error: + # SLA validation failed in scenario, determine what to do now + if sla_action == "assert": + raise + elif sla_action == "monitor": + LOG.warning("SLA validation failed: %s", error.args) + errors = error.args + # catch all exceptions because with multiprocessing we can have un-picklable exception + # problems https://bugs.python.org/issue9400 + except Exception: # pylint: disable=broad-except + errors = traceback.format_exc() + LOG.exception("") + else: + if result: + # add timeout for put so we don't block test + # if we do timeout we don't care about dropping individual KPIs + output_queue.put(result, True, constants.QUEUE_PUT_TIMEOUT) + + benchmark_output = { + 'timestamp': time.time(), + 'sequence': sequence, + 'data': data, + 'errors': errors + } + + queue.put(benchmark_output, True, constants.QUEUE_PUT_TIMEOUT) + else: + LOG.debug("No sample collected ...Sequence %s", sequence) + + + sequence += 1 + + if ((errors and sla_action is None) or time.time() > timeout + or aborted.is_set() or benchmark.is_ended()): + LOG.info("Worker END") + break + + try: + benchmark.teardown() + except Exception: + # catch any exception in teardown and convert to simple exception + # never pass exceptions back to multiprocessing, because some exceptions can + # be unpicklable + # https://bugs.python.org/issue9400 + LOG.exception("") + raise SystemExit(1) + + LOG.debug("queue.qsize() = %s", queue.qsize()) + LOG.debug("output_queue.qsize() = %s", output_queue.qsize()) + LOG.info("Exiting ProxDuration Runner...") + +class ProxDurationRunner(base.Runner): + """Run a scenario for a certain amount of time + +If the scenario ends before the time has elapsed, it will be started again. + + Parameters + duration - amount of time the scenario will be run for + type: int + unit: seconds + default: 60 sec + interval - time to wait between each scenario invocation + type: int + unit: seconds + default: 1 sec + sampled - Sample data is required yes/no + type: boolean + unit: True/False + default: False + confirmation - Number of confirmation retries + type: int + unit: retry attempts + default: 0 + """ + __execution_type__ = 'ProxDuration' + + def _run_benchmark(self, cls, method, scenario_cfg, context_cfg): + name = "{}-{}-{}".format(self.__execution_type__, scenario_cfg.get("type"), os.getpid()) + self.process = multiprocessing.Process( + name=name, + target=_worker_process, + args=(self.result_queue, cls, method, scenario_cfg, + context_cfg, self.aborted, self.output_queue)) + self.process.start() diff --git a/yardstick/benchmark/runners/search.py b/yardstick/benchmark/runners/search.py index 8037329b5..01a4292c7 100644 --- a/yardstick/benchmark/runners/search.py +++ b/yardstick/benchmark/runners/search.py @@ -33,6 +33,7 @@ from collections import Mapping from six.moves import zip from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -119,14 +120,14 @@ If the scenario ends before the time has elapsed, it will be started again. try: self.worker_helper(data) - except AssertionError as assertion: + except y_exc.SLAValidationError as error: # SLA validation failed in scenario, determine what to do now if self.sla_action == "assert": raise elif self.sla_action == "monitor": - LOG.warning("SLA validation failed: %s", assertion.args) - errors = assertion.args - except Exception as e: + LOG.warning("SLA validation failed: %s", error.args) + errors = error.args + except Exception as e: # pylint: disable=broad-except errors = traceback.format_exc() LOG.exception(e) diff --git a/yardstick/benchmark/runners/sequence.py b/yardstick/benchmark/runners/sequence.py index d6e3f7109..58ffddd22 100644 --- a/yardstick/benchmark/runners/sequence.py +++ b/yardstick/benchmark/runners/sequence.py @@ -30,6 +30,7 @@ import traceback import os from yardstick.benchmark.runners import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -37,8 +38,6 @@ LOG = logging.getLogger(__name__) def _worker_process(queue, cls, method_name, scenario_cfg, context_cfg, aborted, output_queue): - sequence = 1 - runner_cfg = scenario_cfg['runner'] interval = runner_cfg.get("interval", 1) @@ -55,6 +54,7 @@ def _worker_process(queue, cls, method_name, scenario_cfg, LOG.info("worker START, sequence_values(%s, %s), class %s", arg_name, sequence_values, cls) + scenario_output = base.ScenarioOutput(queue, sequence=1, errors="") benchmark = cls(scenario_cfg, context_cfg) benchmark.setup() method = getattr(benchmark, method_name) @@ -67,22 +67,23 @@ def _worker_process(queue, cls, method_name, scenario_cfg, options[arg_name] = value LOG.debug("runner=%(runner)s seq=%(sequence)s START", - {"runner": runner_cfg["runner_id"], "sequence": sequence}) + {"runner": runner_cfg["runner_id"], + "sequence": scenario_output.sequence}) - data = {} - errors = "" + scenario_output.clear() + scenario_output.errors = "" try: - result = method(data) - except AssertionError as assertion: + result = method(scenario_output) + except y_exc.SLAValidationError as error: # SLA validation failed in scenario, determine what to do now if sla_action == "assert": raise elif sla_action == "monitor": - LOG.warning("SLA validation failed: %s", assertion.args) - errors = assertion.args - except Exception as e: - errors = traceback.format_exc() + LOG.warning("SLA validation failed: %s", error.args) + scenario_output.errors = error.args + except Exception as e: # pylint: disable=broad-except + scenario_output.errors = traceback.format_exc() LOG.exception(e) else: if result: @@ -90,21 +91,16 @@ def _worker_process(queue, cls, method_name, scenario_cfg, time.sleep(interval) - benchmark_output = { - 'timestamp': time.time(), - 'sequence': sequence, - 'data': data, - 'errors': errors - } - - queue.put(benchmark_output) + if scenario_output: + scenario_output.push() LOG.debug("runner=%(runner)s seq=%(sequence)s END", - {"runner": runner_cfg["runner_id"], "sequence": sequence}) + {"runner": runner_cfg["runner_id"], + "sequence": scenario_output.sequence}) - sequence += 1 + scenario_output.sequence += 1 - if (errors and sla_action is None) or aborted.is_set(): + if (scenario_output.errors and sla_action is None) or aborted.is_set(): break try: diff --git a/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py b/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py index 979e3ab14..4c79a4931 100644 --- a/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py +++ b/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py @@ -23,7 +23,7 @@ def _execute_shell_command(command, stdin=None): output = [] try: output = subprocess.check_output(command, stdin=stdin, shell=True) - except Exception: + except Exception: # pylint: disable=broad-except exitcode = -1 LOG.error("exec command '%s' error:\n ", command, exc_info=True) @@ -34,6 +34,8 @@ class BaremetalAttacker(BaseAttacker): __attacker_type__ = 'bare-metal-down' def setup(self): + # baremetal down need to recover even sla pass + self.mandatory = True LOG.debug("config:%s context:%s", self._config, self._context) host = self._context.get(self._config['host'], None) @@ -49,8 +51,7 @@ class BaremetalAttacker(BaseAttacker): LOG.debug("jump_host ip:%s user:%s", jump_host['ip'], jump_host['user']) self.jump_connection = ssh.SSH.from_node( jump_host, - # why do we allow pwd for password? - defaults={"user": "root", "password": jump_host.get("pwd")} + defaults={"user": "root", "password": jump_host.get("password")} ) self.jump_connection.wait(timeout=600) LOG.debug("ssh jump host success!") @@ -59,7 +60,7 @@ class BaremetalAttacker(BaseAttacker): self.ipmi_ip = host.get("ipmi_ip", None) self.ipmi_user = host.get("ipmi_user", "root") - self.ipmi_pwd = host.get("ipmi_pwd", None) + self.ipmi_pwd = host.get("ipmi_password", None) self.fault_cfg = BaseAttacker.attacker_cfgs.get('bare-metal-down') self.check_script = self.get_script_fullpath( @@ -107,26 +108,3 @@ class BaremetalAttacker(BaseAttacker): else: _execute_shell_command(cmd, stdin=stdin_file) LOG.info("Recover fault END") - - -def _test(): # pragma: no cover - host = { - "ipmi_ip": "10.20.0.5", - "ipmi_user": "root", - "ipmi_pwd": "123456", - "ip": "10.20.0.5", - "user": "root", - "key_filename": "/root/.ssh/id_rsa" - } - context = {"node1": host} - attacker_cfg = { - 'fault_type': 'bear-metal-down', - 'host': 'node1', - } - ins = BaremetalAttacker(attacker_cfg, context) - ins.setup() - ins.inject_fault() - - -if __name__ == '__main__': # pragma: no cover - _test() diff --git a/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py b/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py index cb171eafa..7f1136c08 100644 --- a/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py +++ b/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py @@ -42,29 +42,28 @@ class ProcessAttacker(BaseAttacker): def check(self): with open(self.check_script, "r") as stdin_file: - exit_status, stdout, stderr = self.connection.execute( + _, stdout, stderr = self.connection.execute( "sudo /bin/sh -s {0}".format(self.service_name), stdin=stdin_file) if stdout: - LOG.info("check the environment success!") + LOG.info("Check the environment success!") return int(stdout.strip('\n')) else: - LOG.error( - "the host environment is error, stdout:%s, stderr:%s", - stdout, stderr) + LOG.error("Error checking the host environment, " + "stdout:%s, stderr:%s", stdout, stderr) return False def inject_fault(self): with open(self.inject_script, "r") as stdin_file: - exit_status, stdout, stderr = self.connection.execute( + self.connection.execute( "sudo /bin/sh -s {0}".format(self.service_name), stdin=stdin_file) def recover(self): with open(self.recovery_script, "r") as stdin_file: - exit_status, stdout, stderr = self.connection.execute( + exit_status, _, _ = self.connection.execute( "sudo /bin/bash -s {0} ".format(self.service_name), stdin=stdin_file) if exit_status: - LOG.info("Fail to restart service!") + LOG.info("Failed to restart service: %s", self.recovery_script) diff --git a/yardstick/benchmark/scenarios/availability/attacker/baseattacker.py b/yardstick/benchmark/scenarios/availability/attacker/baseattacker.py index d03d04420..7871cc918 100644 --- a/yardstick/benchmark/scenarios/availability/attacker/baseattacker.py +++ b/yardstick/benchmark/scenarios/availability/attacker/baseattacker.py @@ -63,6 +63,7 @@ class BaseAttacker(object): self.data = {} self.setup_done = False self.intermediate_variables = {} + self.mandatory = False @staticmethod def get_attacker_cls(attacker_cfg): @@ -71,7 +72,7 @@ class BaseAttacker(object): for attacker_cls in utils.itersubclasses(BaseAttacker): if attacker_type == attacker_cls.__attacker_type__: return attacker_cls - raise RuntimeError("No such runner_type %s" % attacker_type) + raise RuntimeError("No such runner_type: %s" % attacker_type) def get_script_fullpath(self, path): base_path = os.path.dirname(attacker_conf_path) diff --git a/yardstick/benchmark/scenarios/availability/director.py b/yardstick/benchmark/scenarios/availability/director.py index 71690c135..6cc0cb286 100644 --- a/yardstick/benchmark/scenarios/availability/director.py +++ b/yardstick/benchmark/scenarios/availability/director.py @@ -40,7 +40,7 @@ class Director(object): nodes = self.context_cfg.get("nodes", None) # setup attackers if "attackers" in self.scenario_cfg["options"]: - LOG.debug("start init attackers...") + LOG.debug("Start init attackers...") attacker_cfgs = self.scenario_cfg["options"]["attackers"] self.attackerMgr = baseattacker.AttackerMgr() self.data = self.attackerMgr.init_attackers(attacker_cfgs, @@ -48,19 +48,19 @@ class Director(object): # setup monitors if "monitors" in self.scenario_cfg["options"]: - LOG.debug("start init monitors...") + LOG.debug("Start init monitors...") monitor_cfgs = self.scenario_cfg["options"]["monitors"] self.monitorMgr = basemonitor.MonitorMgr(self.data) self.monitorMgr.init_monitors(monitor_cfgs, nodes) # setup operations if "operations" in self.scenario_cfg["options"]: - LOG.debug("start init operations...") + LOG.debug("Start init operations...") operation_cfgs = self.scenario_cfg["options"]["operations"] self.operationMgr = baseoperation.OperationMgr() self.operationMgr.init_operations(operation_cfgs, nodes) # setup result checker if "resultCheckers" in self.scenario_cfg["options"]: - LOG.debug("start init resultCheckers...") + LOG.debug("Start init resultCheckers...") result_check_cfgs = self.scenario_cfg["options"]["resultCheckers"] self.resultCheckerMgr = baseresultchecker.ResultCheckerMgr() self.resultCheckerMgr.init_ResultChecker(result_check_cfgs, nodes) @@ -69,7 +69,7 @@ class Director(object): if intermediate_variables is None: intermediate_variables = {} LOG.debug( - "the type of current action is %s, the key is %s", type, key) + "The type of current action is %s, the key is %s", type, key) if type == ActionType.ATTACKER: return actionplayers.AttackerPlayer(self.attackerMgr[key], intermediate_variables) if type == ActionType.MONITOR: @@ -80,17 +80,17 @@ class Director(object): if type == ActionType.OPERATION: return actionplayers.OperationPlayer(self.operationMgr[key], intermediate_variables) - LOG.debug("something run when creatactionplayer") + LOG.debug("The type is not recognized by createActionPlayer") def createActionRollbacker(self, type, key): LOG.debug( - "the type of current action is %s, the key is %s", type, key) + "The type of current action is %s, the key is %s", type, key) if type == ActionType.ATTACKER: return actionrollbackers.AttackerRollbacker(self.attackerMgr[key]) if type == ActionType.OPERATION: return actionrollbackers.OperationRollbacker( self.operationMgr[key]) - LOG.debug("no rollbacker created for %s", key) + LOG.debug("No rollbacker created for key: %s", key) def verify(self): result = True @@ -99,7 +99,7 @@ class Director(object): if hasattr(self, 'resultCheckerMgr'): result &= self.resultCheckerMgr.verify() if result: - LOG.debug("monitors are passed") + LOG.debug("Monitor results are passed") return result def stopMonitors(self): @@ -107,12 +107,12 @@ class Director(object): self.monitorMgr.wait_monitors() def knockoff(self): - LOG.debug("knock off ....") + LOG.debug("Knock off ....") while self.executionSteps: singleStep = self.executionSteps.pop() singleStep.rollback() def store_result(self, result): - LOG.debug("store result ....") + LOG.debug("Store result ....") if hasattr(self, 'monitorMgr'): self.monitorMgr.store_result(result) diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/fault_process_kill.bash b/yardstick/benchmark/scenarios/availability/ha_tools/fault_process_kill.bash index d34ce9338..cda469cf9 100755 --- a/yardstick/benchmark/scenarios/availability/ha_tools/fault_process_kill.bash +++ b/yardstick/benchmark/scenarios/availability/ha_tools/fault_process_kill.bash @@ -16,7 +16,7 @@ set -e process_name=$1 if [ "$process_name" = "keystone" ]; then - for pid in $(ps aux | grep "keystone" | grep -iv heartbeat | grep -iv monitor | grep -v grep | grep -v /bin/sh | awk '{print $2}'); \ + for pid in $(ps aux | grep "keystone" | grep -iv monitor | grep -v grep | grep -v /bin/sh | awk '{print $2}'); \ do kill -9 "${pid}" done @@ -26,7 +26,7 @@ elif [ "$process_name" = "haproxy" ]; then kill -9 "${pid}" done else - for pid in $(pgrep -fa [^-_a-zA-Z0-9]${process_name} | grep -iv heartbeat | awk '{print $1}'); + for pid in $(pgrep -fa [^-_a-zA-Z0-9]${process_name} | awk '{print $1}'); do kill -9 "${pid}" done diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/add_server_to_existing_secgroup.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/add_server_to_existing_secgroup.bash new file mode 100644 index 000000000..3a50626f5 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/add_server_to_existing_secgroup.bash @@ -0,0 +1,26 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2018 Intracom Telecom and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# add server to existing security group +# parameters: $1 - server name, $2 - security group name + +set -e + +if [ $OS_INSECURE ] && [ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]; then + SECURE="--insecure" +else + SECURE="" +fi + +SECGROUPNAME="$(openstack ${SECURE} security group list -f value -c Name | grep $2)" + +openstack ${SECURE} server add security group $1 ${SECGROUPNAME} + diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_instance_from_image.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_instance_from_image.bash new file mode 100644 index 000000000..5e0b1ccf1 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_instance_from_image.bash @@ -0,0 +1,26 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2018 Intracom Telecom and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# create nova server +# parameters: $1 - server name, $2 - image name, $3 - flavor name, $4 - network name + +set -e + +if [ $OS_INSECURE ] && [ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]; then + SECURE="--insecure" +else + SECURE="" +fi + +NETNAME="$(openstack ${SECURE} network list -f value -c Name | grep $4)" + +openstack ${SECURE} server create $1 --image $2 --flavor $3 --network ${NETNAME} + diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_instance.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_instance.bash new file mode 100644 index 000000000..008e7f5ff --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_instance.bash @@ -0,0 +1,24 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2018 Intracom Telecom and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# delete nova server +# parameters: $1 - server name + +set -e + +if [ $OS_INSECURE ] && [ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]; then + SECURE="--insecure" +else + SECURE="" +fi + +openstack ${SECURE} server delete $1 + diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/get_server_privateip.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/get_server_privateip.bash new file mode 100644 index 000000000..7f2bad540 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/get_server_privateip.bash @@ -0,0 +1,24 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2018 Intracom Telecom and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# get private ip of a server +# parameter: $1 - server name + +set -e + +if [ $OS_INSECURE ] && [ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]; then + SECURE="--insecure" +else + SECURE="" +fi + +openstack ${SECURE} server list -f value -c Name -c Networks | grep $1 | awk '{print $2}' | sed -r 's/.*=([0-9\.\:]+)[;,]*/\1/' + diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/remove_server_from_secgroup.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/remove_server_from_secgroup.bash new file mode 100644 index 000000000..61d0a2b49 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/remove_server_from_secgroup.bash @@ -0,0 +1,25 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2018 Intracom Telecom and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# remove server from existing security group +# parameters: $1 - server name, $2 - security group name + +set -e + +if [ $OS_INSECURE ] && [ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]; then + SECURE="--insecure" +else + SECURE="" +fi + +SECGROUPNAME="$(openstack ${SECURE} security group list -f value -c Name | grep $2)" + +openstack ${SECURE} server remove security group $1 ${SECGROUPNAME} diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/start_service.bash b/yardstick/benchmark/scenarios/availability/ha_tools/start_service.bash index 858d86ca0..2388507d7 100755 --- a/yardstick/benchmark/scenarios/availability/ha_tools/start_service.bash +++ b/yardstick/benchmark/scenarios/availability/ha_tools/start_service.bash @@ -9,24 +9,23 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -# Start a service and check the service is started +# Start or restart a service and check the service is started set -e service_name=$1 +operation=${2-start} # values are "start" or "restart" -Distributor=$(lsb_release -a | grep "Distributor ID" | awk '{print $3}') - -if [ "$Distributor" != "Ubuntu" -a "$service_name" != "keystone" -a "$service_name" != "neutron-server" -a "$service_name" != "haproxy" ]; then +if [ -f /usr/bin/yum -a "$service_name" != "keystone" -a "$service_name" != "neutron-server" -a "$service_name" != "haproxy" -a "$service_name" != "openvswitch" ]; then service_name="openstack-"${service_name} -elif [ "$Distributor" = "Ubuntu" -a "$service_name" = "keystone" ]; then +elif [ -f /usr/bin/apt -a "$service_name" = "keystone" ]; then service_name="apache2" elif [ "$service_name" = "keystone" ]; then service_name="httpd" fi if which systemctl 2>/dev/null; then - systemctl start $service_name + systemctl $operation $service_name else - service $service_name start + service $service_name $operation fi diff --git a/yardstick/benchmark/scenarios/availability/monitor/basemonitor.py b/yardstick/benchmark/scenarios/availability/monitor/basemonitor.py index 50a63f53d..f6004c774 100644 --- a/yardstick/benchmark/scenarios/availability/monitor/basemonitor.py +++ b/yardstick/benchmark/scenarios/availability/monitor/basemonitor.py @@ -103,7 +103,7 @@ class BaseMonitor(multiprocessing.Process): for monitor in utils.itersubclasses(BaseMonitor): if monitor_type == monitor.__monitor_type__: return monitor - raise RuntimeError("No such monitor_type %s" % monitor_type) + raise RuntimeError("No such monitor_type: %s" % monitor_type) def get_script_fullpath(self, path): base_path = os.path.dirname(monitor_conf_path) diff --git a/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py b/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py index d0551bf03..3b36c762d 100644 --- a/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py +++ b/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py @@ -24,7 +24,7 @@ def _execute_shell_command(command): output = [] try: output = subprocess.check_output(command, shell=True) - except Exception: + except Exception: # pylint: disable=broad-except exitcode = -1 LOG.error("exec command '%s' error:\n ", command, exc_info=True) @@ -45,7 +45,7 @@ class MonitorOpenstackCmd(basemonitor.BaseMonitor): self.connection = ssh.SSH.from_node(host, defaults={"user": "root"}) self.connection.wait(timeout=600) - LOG.debug("ssh host success!") + LOG.debug("ssh host (%s) success!", str(host)) self.check_script = self.get_script_fullpath( "ha_tools/check_openstack_cmd.bash") @@ -61,22 +61,20 @@ class MonitorOpenstackCmd(basemonitor.BaseMonitor): self.cmd = self.cmd + " --insecure" def monitor_func(self): - exit_status = 0 exit_status, stdout = _execute_shell_command(self.cmd) - LOG.debug("Execute command '%s' and the stdout is:\n%s", self.cmd, stdout) + LOG.debug("Executed command '%s'. " + "The stdout is:\n%s", self.cmd, stdout) if exit_status: return False return True def verify_SLA(self): outage_time = self._result.get('outage_time', None) - LOG.debug("the _result:%s", self._result) max_outage_time = self._config["sla"]["max_outage_time"] if outage_time > max_outage_time: LOG.info("SLA failure: %f > %f", outage_time, max_outage_time) return False else: - LOG.info("the sla is passed") return True @@ -97,7 +95,7 @@ def _test(): # pragma: no cover } monitor_configs.append(config) - p = basemonitor.MonitorMgr() + p = basemonitor.MonitorMgr({}) p.init_monitors(monitor_configs, context) p.start_monitors() p.wait_monitors() diff --git a/yardstick/benchmark/scenarios/availability/monitor/monitor_multi.py b/yardstick/benchmark/scenarios/availability/monitor/monitor_multi.py index dce69f45f..8f1f53cde 100644 --- a/yardstick/benchmark/scenarios/availability/monitor/monitor_multi.py +++ b/yardstick/benchmark/scenarios/availability/monitor/monitor_multi.py @@ -29,7 +29,7 @@ class MultiMonitor(basemonitor.BaseMonitor): monitor_cls = basemonitor.BaseMonitor.get_monitor_cls(monitor_type) monitor_number = self._config.get("monitor_number", 1) - for i in range(monitor_number): + for _ in range(monitor_number): monitor_ins = monitor_cls(self._config, self._context, self.monitor_data) self.monitors.append(monitor_ins) @@ -62,19 +62,19 @@ class MultiMonitor(basemonitor.BaseMonitor): outage_time = ( last_outage - first_outage if last_outage > first_outage else 0 ) + self._result = {"outage_time": outage_time} LOG.debug("outage_time is: %f", outage_time) max_outage_time = 0 - if "max_outage_time" in self._config["sla"]: - max_outage_time = self._config["sla"]["max_outage_time"] - elif "max_recover_time" in self._config["sla"]: - max_outage_time = self._config["sla"]["max_recover_time"] - else: - raise RuntimeError("monitor max_outage_time config is not found") - self._result = {"outage_time": outage_time} - - if outage_time > max_outage_time: - LOG.error("SLA failure: %f > %f", outage_time, max_outage_time) - return False - else: - return True + if self._config.get("sla"): + if "max_outage_time" in self._config["sla"]: + max_outage_time = self._config["sla"]["max_outage_time"] + elif "max_recover_time" in self._config["sla"]: + max_outage_time = self._config["sla"]["max_recover_time"] + else: + raise RuntimeError("'max_outage_time' or 'max_recover_time' " + "config is not found") + if outage_time > max_outage_time: + LOG.error("SLA failure: %f > %f", outage_time, max_outage_time) + return False + return True diff --git a/yardstick/benchmark/scenarios/availability/monitor/monitor_process.py b/yardstick/benchmark/scenarios/availability/monitor/monitor_process.py index b0f6f8e9d..280e5811d 100644 --- a/yardstick/benchmark/scenarios/availability/monitor/monitor_process.py +++ b/yardstick/benchmark/scenarios/availability/monitor/monitor_process.py @@ -25,14 +25,14 @@ class MonitorProcess(basemonitor.BaseMonitor): self.connection = ssh.SSH.from_node(host, defaults={"user": "root"}) self.connection.wait(timeout=600) - LOG.debug("ssh host success!") + LOG.debug("ssh host (%s) success!", str(host)) self.check_script = self.get_script_fullpath( "ha_tools/check_process_python.bash") self.process_name = self._config["process_name"] def monitor_func(self): with open(self.check_script, "r") as stdin_file: - exit_status, stdout, stderr = self.connection.execute( + _, stdout, _ = self.connection.execute( "sudo /bin/sh -s {0}".format(self.process_name), stdin=stdin_file) @@ -45,15 +45,13 @@ class MonitorProcess(basemonitor.BaseMonitor): return True def verify_SLA(self): - LOG.debug("the _result:%s", self._result) outage_time = self._result.get('outage_time', None) - max_outage_time = self._config["sla"]["max_recover_time"] - if outage_time > max_outage_time: - LOG.error("SLA failure: %f > %f", outage_time, max_outage_time) - return False - else: - LOG.info("the sla is passed") - return True + if self._config.get("sla"): + max_outage_time = self._config["sla"]["max_recover_time"] + if outage_time > max_outage_time: + LOG.info("SLA failure: %f > %f", outage_time, max_outage_time) + return False + return True def _test(): # pragma: no cover @@ -73,7 +71,7 @@ def _test(): # pragma: no cover } monitor_configs.append(config) - p = basemonitor.MonitorMgr() + p = basemonitor.MonitorMgr({}) p.init_monitors(monitor_configs, context) p.start_monitors() p.wait_monitors() diff --git a/yardstick/benchmark/scenarios/availability/operation_conf.yaml b/yardstick/benchmark/scenarios/availability/operation_conf.yaml index dc5169196..5f3f6c91e 100644 --- a/yardstick/benchmark/scenarios/availability/operation_conf.yaml +++ b/yardstick/benchmark/scenarios/availability/operation_conf.yaml @@ -35,3 +35,14 @@ get-vip-host: action_script: ha_tools/pacemaker/get_vip_host.bash rollback_script: ha_tools/pacemaker/get_resource_status.bash +start-service: + action_script: ha_tools/start_service.bash + rollback_script: ha_tools/check_process_python.bash + +add-server-to-secgroup: + action_script: ha_tools/nova/add_server_to_existing_secgroup.bash + rollback_script: ha_tools/nova/remove_server_from_secgroup.bash + +get-privateip: + action_script: ha_tools/nova/get_server_privateip.bash + rollback_script: ha_tools/nova/list_servers.bash diff --git a/yardstick/benchmark/scenarios/availability/scenario_general.py b/yardstick/benchmark/scenarios/availability/scenario_general.py index 9ac55471d..e2db03a70 100644 --- a/yardstick/benchmark/scenarios/availability/scenario_general.py +++ b/yardstick/benchmark/scenarios/availability/scenario_general.py @@ -26,7 +26,6 @@ class ScenarioGeneral(base.Scenario): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg self.intermediate_variables = {} - self.pass_flag = True def setup(self): self.director = Director(self.scenario_cfg, self.context_cfg) @@ -47,7 +46,7 @@ class ScenarioGeneral(base.Scenario): step['actionType'], step['actionKey']) if actionRollbacker: self.director.executionSteps.append(actionRollbacker) - except Exception: + except Exception: # pylint: disable=broad-except LOG.exception("Exception") LOG.debug( "\033[91m exception when running step: %s .... \033[0m", @@ -59,31 +58,20 @@ class ScenarioGeneral(base.Scenario): self.director.stopMonitors() verify_result = self.director.verify() - - self.director.store_result(result) - + service_not_found = False for k, v in self.director.data.items(): if v == 0: - result['sla_pass'] = 0 verify_result = False - self.pass_flag = False - LOG.info( - "\033[92m The service process not found in the host \ -envrioment, the HA test case NOT pass") + service_not_found = True + LOG.info("\033[92m The service process (%s) not found in the host environment", k) - if verify_result: - result['sla_pass'] = 1 - LOG.info( - "\033[92m Congratulations, " - "the HA test case PASS! \033[0m") - else: - result['sla_pass'] = 0 - self.pass_flag = False - LOG.info( - "\033[91m Aoh, the HA test case FAIL," - "please check the detail debug information! \033[0m") + result['sla_pass'] = 1 if verify_result else 0 + self.director.store_result(result) + + self.verify_SLA( + verify_result, ("a service process was not found in the host " + "environment" if service_not_found + else "Director.verify() failed")) def teardown(self): self.director.knockoff() - - assert self.pass_flag, "The HA test case NOT passed" diff --git a/yardstick/benchmark/scenarios/availability/serviceha.py b/yardstick/benchmark/scenarios/availability/serviceha.py index 6d0d812af..fdfe7cbbe 100755 --- a/yardstick/benchmark/scenarios/availability/serviceha.py +++ b/yardstick/benchmark/scenarios/availability/serviceha.py @@ -29,13 +29,13 @@ class ServiceHA(base.Scenario): self.context_cfg = context_cfg self.setup_done = False self.data = {} - self.pass_flag = True + self.sla_pass = False def setup(self): """scenario setup""" nodes = self.context_cfg.get("nodes", None) if nodes is None: - LOG.error("the nodes info is none") + LOG.error("The nodes info is none") return self.attackers = [] @@ -58,43 +58,40 @@ class ServiceHA(base.Scenario): def run(self, result): """execute the benchmark""" if not self.setup_done: - LOG.error("The setup not finished!") + LOG.error("The setup is not finished!") return self.monitorMgr.start_monitors() - LOG.info("HA monitor start!") + LOG.info("Monitor '%s' start!", self.__scenario_type__) for attacker in self.attackers: attacker.inject_fault() self.monitorMgr.wait_monitors() - LOG.info("HA monitor stop!") + LOG.info("Monitor '%s' stop!", self.__scenario_type__) - sla_pass = self.monitorMgr.verify_SLA() + self.sla_pass = self.monitorMgr.verify_SLA() + service_not_found = False for k, v in self.data.items(): if v == 0: - result['sla_pass'] = 0 - self.pass_flag = False - LOG.info("The service process not found in the host envrioment, \ -the HA test case NOT pass") - return + self.sla_pass = False + service_not_found = True + LOG.info("The service process (%s) not found in the host envrioment", k) + + result['sla_pass'] = 1 if self.sla_pass else 0 self.monitorMgr.store_result(result) - if sla_pass: - result['sla_pass'] = 1 - LOG.info("The HA test case PASS the SLA") - else: - result['sla_pass'] = 0 - self.pass_flag = False - assert sla_pass is True, "The HA test case NOT pass the SLA" - return + self.verify_SLA( + self.sla_pass, ("a service process was not found in the host " + "environment" if service_not_found + else "MonitorMgr.verify_SLA() failed")) def teardown(self): """scenario teardown""" + # recover when mandatory or sla not pass for attacker in self.attackers: - attacker.recover() - - assert self.pass_flag, "The HA test case NOT passed" + if attacker.mandatory or not self.sla_pass: + attacker.recover() def _test(): # pragma: no cover diff --git a/yardstick/benchmark/scenarios/base.py b/yardstick/benchmark/scenarios/base.py index 7af85834c..ae8bfad71 100644 --- a/yardstick/benchmark/scenarios/base.py +++ b/yardstick/benchmark/scenarios/base.py @@ -13,44 +13,76 @@ # License for the specific language governing permissions and limitations # under the License. -# yardstick comment: this is a modified copy of -# rally/rally/benchmark/scenarios/base.py +import abc +import time -""" Scenario base class -""" +import six +from stevedore import extension -from __future__ import absolute_import import yardstick.common.utils as utils +from yardstick.common import exceptions as y_exc +def _iter_scenario_classes(scenario_type=None): + """Generator over all 'Scenario' subclasses + + This function will iterate over all 'Scenario' subclasses defined in this + project and will load any class introduced by any installed plugin project, + defined in 'entry_points' section, under 'yardstick.scenarios' subsection. + """ + extension.ExtensionManager(namespace='yardstick.scenarios', + invoke_on_load=False) + for scenario in utils.itersubclasses(Scenario): + if not scenario_type: + yield scenario + elif getattr(scenario, '__scenario_type__', None) == scenario_type: + yield scenario + + +@six.add_metaclass(abc.ABCMeta) class Scenario(object): def setup(self): - """ default impl for scenario setup """ + """Default setup implementation for Scenario classes""" pass - def run(self, args): - """ catcher for not implemented run methods in subclasses """ - raise RuntimeError("run method not implemented") + @abc.abstractmethod + def run(self, *args): + """Entry point for scenario classes, called from runner worker""" + + def is_ended(self): + return False def teardown(self): - """ default impl for scenario teardown """ + """Default teardown implementation for Scenario classes""" + pass + + def pre_run_wait_time(self, time_seconds): + """Time waited before executing the run method""" pass + def post_run_wait_time(self, time_seconds): + """Time waited after executing the run method""" + time.sleep(time_seconds) + + def verify_SLA(self, condition, error_msg): + if not condition: + raise y_exc.SLAValidationError( + case_name=self.__scenario_type__, error_msg=error_msg) + @staticmethod def get_types(): """return a list of known runner type (class) names""" scenarios = [] - for scenario in utils.itersubclasses(Scenario): + for scenario in _iter_scenario_classes(): scenarios.append(scenario) return scenarios @staticmethod def get_cls(scenario_type): """return class of specified type""" - for scenario in utils.itersubclasses(Scenario): - if scenario_type == scenario.__scenario_type__: - return scenario + for scenario in _iter_scenario_classes(scenario_type): + return scenario raise RuntimeError("No such scenario type %s" % scenario_type) @@ -58,11 +90,8 @@ class Scenario(object): def get(scenario_type): """Returns instance of a scenario runner for execution type. """ - for scenario in utils.itersubclasses(Scenario): - if scenario_type == scenario.__scenario_type__: - return scenario.__module__ + "." + scenario.__name__ - - raise RuntimeError("No such scenario type %s" % scenario_type) + scenario = Scenario.get_cls(scenario_type) + return scenario.__module__ + "." + scenario.__name__ @classmethod def get_scenario_type(cls): @@ -78,10 +107,14 @@ class Scenario(object): """ return cls.__doc__.splitlines()[0] if cls.__doc__ else str(None) - def _push_to_outputs(self, keys, values): + @staticmethod + def _push_to_outputs(keys, values): + """Return a dictionary given the keys and the values""" return dict(zip(keys, values)) - def _change_obj_to_dict(self, obj): + @staticmethod + def _change_obj_to_dict(obj): + """Return a dictionary from the __dict__ attribute of an object""" dic = {} for k, v in vars(obj).items(): try: diff --git a/yardstick/benchmark/scenarios/compute/cyclictest.py b/yardstick/benchmark/scenarios/compute/cyclictest.py index 998463ef6..413709f3b 100644 --- a/yardstick/benchmark/scenarios/compute/cyclictest.py +++ b/yardstick/benchmark/scenarios/compute/cyclictest.py @@ -100,7 +100,7 @@ class Cyclictest(base.Scenario): def _run_setup_cmd(self, client, cmd): LOG.debug("Run cmd: %s", cmd) - status, stdout, stderr = client.execute(cmd) + status, _, stderr = client.execute(cmd) if status: if re.search(self.REBOOT_CMD_PATTERN, cmd): LOG.debug("Error on reboot") @@ -195,7 +195,7 @@ class Cyclictest(base.Scenario): if latency > sla_latency: sla_error += "%s latency %d > sla:max_%s_latency(%d); " % \ (t, latency, t, sla_latency) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) def _test(): # pragma: no cover diff --git a/yardstick/benchmark/scenarios/compute/lmbench.py b/yardstick/benchmark/scenarios/compute/lmbench.py index 801f7fa80..2237e49e0 100644 --- a/yardstick/benchmark/scenarios/compute/lmbench.py +++ b/yardstick/benchmark/scenarios/compute/lmbench.py @@ -119,8 +119,8 @@ class Lmbench(base.Scenario): cmd = "sudo bash lmbench_latency_for_cache.sh %d %d" % \ (repetition, warmup) else: - raise RuntimeError("No such test_type: %s for Lmbench scenario", - test_type) + raise RuntimeError("No such test_type: %s for Lmbench scenario" + % test_type) LOG.debug("Executing command: %s", cmd) status, stdout, stderr = self.client.execute(cmd) @@ -157,7 +157,7 @@ class Lmbench(base.Scenario): if sla_latency < cache_latency: sla_error += "latency %f > sla:max_latency(%f); " \ % (cache_latency, sla_latency) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) def _test(): diff --git a/yardstick/benchmark/scenarios/compute/perf.py b/yardstick/benchmark/scenarios/compute/perf.py index 0b8ed9b28..b973211f1 100644 --- a/yardstick/benchmark/scenarios/compute/perf.py +++ b/yardstick/benchmark/scenarios/compute/perf.py @@ -93,7 +93,7 @@ class Perf(base.Scenario): % (load, duration, events_string) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) + status, stdout, _ = self.client.execute(cmd) if status: raise RuntimeError(stdout) @@ -105,16 +105,14 @@ class Perf(base.Scenario): exp_val = self.scenario_cfg['sla']['expected_value'] smaller_than_exp = 'smaller_than_expected' \ in self.scenario_cfg['sla'] - - if metric not in result: - assert False, "Metric (%s) not found." % metric - else: - if smaller_than_exp: - assert result[metric] < exp_val, "%s %d >= %d (sla); " \ - % (metric, result[metric], exp_val) - else: - assert result[metric] >= exp_val, "%s %d < %d (sla); " \ - % (metric, result[metric], exp_val) + self.verify_SLA(metric in result, + "Metric (%s) not found." % metric) + self.verify_SLA( + not smaller_than_exp, + "%s %d >= %d (sla); " % (metric, result[metric], exp_val)) + self.verify_SLA( + result[metric] >= exp_val, + "%s %d < %d (sla); " % (metric, result[metric], exp_val)) def _test(): diff --git a/yardstick/benchmark/scenarios/compute/qemu_migrate.py b/yardstick/benchmark/scenarios/compute/qemu_migrate.py index 2de1270ef..975c90b22 100644 --- a/yardstick/benchmark/scenarios/compute/qemu_migrate.py +++ b/yardstick/benchmark/scenarios/compute/qemu_migrate.py @@ -56,7 +56,7 @@ class QemuMigrate(base.Scenario): def _run_setup_cmd(self, client, cmd): LOG.debug("Run cmd: %s", cmd) - status, stdout, stderr = client.execute(cmd) + status, _, stderr = client.execute(cmd) if status: if re.search(self.REBOOT_CMD_PATTERN, cmd): LOG.debug("Error on reboot") @@ -127,7 +127,7 @@ class QemuMigrate(base.Scenario): if timevalue > sla_time: sla_error += "%s timevalue %d > sla:max_%s(%d); " % \ (t, timevalue, t, sla_time) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) def _test(): # pragma: no cover diff --git a/yardstick/benchmark/scenarios/compute/ramspeed.py b/yardstick/benchmark/scenarios/compute/ramspeed.py index ca64935dd..4daf776ff 100644 --- a/yardstick/benchmark/scenarios/compute/ramspeed.py +++ b/yardstick/benchmark/scenarios/compute/ramspeed.py @@ -121,8 +121,8 @@ class Ramspeed(base.Scenario): (test_id, load, block_size) # only the test_id 1-6 will be used in this scenario else: - raise RuntimeError("No such type_id: %s for Ramspeed scenario", - test_id) + raise RuntimeError("No such type_id: %s for Ramspeed scenario" + % test_id) LOG.debug("Executing command: %s", cmd) status, stdout, stderr = self.client.execute(cmd) @@ -140,4 +140,4 @@ class Ramspeed(base.Scenario): if bw < sla_min_bw: sla_error += "Bandwidth %f < " \ "sla:min_bandwidth(%f)" % (bw, sla_min_bw) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) diff --git a/yardstick/benchmark/scenarios/compute/unixbench.py b/yardstick/benchmark/scenarios/compute/unixbench.py index cdb345717..3cea31694 100644 --- a/yardstick/benchmark/scenarios/compute/unixbench.py +++ b/yardstick/benchmark/scenarios/compute/unixbench.py @@ -125,7 +125,7 @@ class Unixbench(base.Scenario): if score < sla_score: sla_error += "%s score %f < sla:%s_score(%f); " % \ (t, score, t, sla_score) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) def _test(): # pragma: no cover diff --git a/yardstick/benchmark/scenarios/compute/unixbench_benchmark.bash b/yardstick/benchmark/scenarios/compute/unixbench_benchmark.bash index 5a5dbc394..0f0122e51 100644 --- a/yardstick/benchmark/scenarios/compute/unixbench_benchmark.bash +++ b/yardstick/benchmark/scenarios/compute/unixbench_benchmark.bash @@ -18,15 +18,15 @@ OUTPUT_FILE=/tmp/unixbench-out.log # run unixbench test run_unixbench() { - cd /opt/tempT/UnixBench/ + cd /opt/tempT/UnixBench/UnixBench/ ./Run $OPTIONS > $OUTPUT_FILE } # write the result to stdout in json format output_json() { - single_score=$(awk '/Score/{print $7}' $OUTPUT_FILE | head -1 ) - parallel_score=$(awk '/Score/{print $7}' $OUTPUT_FILE | tail -1 ) + single_score=$(awk '/Score/{print $NF}' $OUTPUT_FILE | head -1 ) + parallel_score=$(awk '/Score/{print $NF}' $OUTPUT_FILE | tail -1 ) echo -e "{ \ \"single_score\":\"$single_score\", \ \"parallel_score\":\"$parallel_score\" \ diff --git a/yardstick/benchmark/scenarios/energy/__init__.py b/yardstick/benchmark/scenarios/energy/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/yardstick/benchmark/scenarios/energy/__init__.py diff --git a/yardstick/benchmark/scenarios/energy/energy.py b/yardstick/benchmark/scenarios/energy/energy.py new file mode 100644 index 000000000..7440835be --- /dev/null +++ b/yardstick/benchmark/scenarios/energy/energy.py @@ -0,0 +1,139 @@ +############################################################################## +# Copyright (c) 2019 Lenovo Group Limited 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 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import +import logging +import requests +import json +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) +logging.captureWarnings(True) + + +class Energy(base.Scenario): + """Get current energy consumption of target host + + This scenario sends a REDFISH request to a host BMC + to request current energy consumption. + The response returns a number of Watts. + Usually this is an average of a rolling windows + taken from server internal sensor. + This is dependant of the server provider. + + This scenario should be used with node context + + As this scenario usually run background with other scenarios, + error of api query or data parse will not terminate task runner. + If any error occured, energy consumption will be set to -1. + + Parameters + None + """ + + __scenario_type__ = "Energy" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.target = self.context_cfg['target'] + self.setup_done = False + self.get_response = False + + def _send_request(self, url): + LOG.info("Send request to %s", url) + pod_auth = (self.target["redfish_user"], self.target["redfish_pwd"]) + response = requests.get(url, auth=pod_auth, verify=False) + return response + + def setup(self): + url = "https://{}/redfish/v1/".format(self.target["redfish_ip"]) + response = self._send_request(url) + if response.status_code != 200: + LOG.info("Don't get right response from %s", url) + self.get_response = False + else: + LOG.info("Get response from %s", url) + self.get_response = True + + self.setup_done = True + + def load_chassis_list(self): + chassis_list = [] + + # Get Chassis list + request_url = "https://" + self.target["redfish_ip"] + request_url += "/redfish/v1/Chassis/" + response = self._send_request(request_url) + if response.status_code != 200: + LOG.info("Do not get proper response from %s", request_url) + return chassis_list + + try: + chassis_data = json.loads(response.text) + except(TypeError, ValueError) as e: + LOG.info("Invalid response data, %s", e) + return chassis_list + + try: + for chassis in chassis_data['Members']: + chassis_list.append(chassis["@odata.id"]) + except KeyError as e: + LOG.info("Error data format of chassis data or invalid key.") + + return chassis_list + + def get_power(self, chassis_uri): + """Get PowerMetter values from Redfish API.""" + if chassis_uri[-1:] != '/': + chassis_uri += '/' + request_url = "https://" + self.target['redfish_ip'] + request_url += chassis_uri + request_url += "Power/" + response = self._send_request(request_url) + if response.status_code != 200: + LOG.info("Do not get proper response from %s", request_url) + power = -1 + return power + + try: + power_metrics = json.loads(response.text) + except(TypeError, ValueError) as e: + LOG.info("Invalid response data, %s", e) + power = -1 + return power + + try: + power = power_metrics["PowerControl"][0]["PowerConsumedWatts"] + except KeyError as e: + LOG.info("Error data format of power metrics or invalid key.") + power = -1 + + return power + + def run(self, result): + """execute the benchmark""" + if not self.setup_done: + self.setup() + chassis_list = self.load_chassis_list() + if not self.get_response or not chassis_list: + power = -1 + data = { + "power": power, + } + result.update(data) + else: + power = 0 + for chassis in chassis_list: + power += self.get_power(chassis) + data = { + "power": power, + } + result.update(data) diff --git a/yardstick/benchmark/scenarios/lib/attach_volume.py b/yardstick/benchmark/scenarios/lib/attach_volume.py index 88124964b..96dd130b1 100644 --- a/yardstick/benchmark/scenarios/lib/attach_volume.py +++ b/yardstick/benchmark/scenarios/lib/attach_volume.py @@ -6,30 +6,31 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) class AttachVolume(base.Scenario): - """Attach a volmeu to an instance""" + """Attach a volume to an instance""" __scenario_type__ = "AttachVolume" def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.server_id = self.options.get("server_id", "TestServer") - self.volume_id = self.options.get("volume_id", None) + self.server_name_or_id = self.options["server_name_or_id"] + self.volume_name_or_id = self.options["volume_name_or_id"] + self.device = self.options.get("device") + self.wait = self.options.get("wait", True) + self.timeout = self.options.get("timeout") + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -44,10 +45,14 @@ class AttachVolume(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.attach_server_volume(self.server_id, - self.volume_id) + status = openstack_utils.attach_volume_to_server( + self.shade_client, self.server_name_or_id, self.volume_name_or_id, + device=self.device, wait=self.wait, timeout=self.timeout) + + if not status: + result.update({"attach_volume": 0}) + LOG.error("Attach volume to server failed!") + raise exceptions.ScenarioAttachVolumeError - if status: - LOG.info("Attach volume to server successful!") - else: - LOG.info("Attach volume to server failed!") + result.update({"attach_volume": 1}) + LOG.info("Attach volume to server successful!") diff --git a/yardstick/benchmark/scenarios/lib/check_value.py b/yardstick/benchmark/scenarios/lib/check_value.py index 759076068..4c9b27df4 100644 --- a/yardstick/benchmark/scenarios/lib/check_value.py +++ b/yardstick/benchmark/scenarios/lib/check_value.py @@ -13,6 +13,7 @@ from __future__ import absolute_import import logging from yardstick.benchmark.scenarios import base +from yardstick.common import exceptions as y_exc LOG = logging.getLogger(__name__) @@ -34,24 +35,18 @@ class CheckValue(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - def run(self, result): + def run(self, _): """execute the test""" op = self.options.get("operator") LOG.debug("options=%s", self.options) value1 = str(self.options.get("value1")) value2 = str(self.options.get("value2")) + if (op == "eq" and value1 != value2) or (op == "ne" and + value1 == value2): + raise y_exc.ValueCheckError( + value1=value1, operator=op, value2=value2) check_result = "PASS" - if op == "eq" and value1 != value2: - LOG.info("value1=%s, value2=%s, error: should equal!!!", value1, - value2) - check_result = "FAIL" - assert value1 == value2, "Error %s!=%s" % (value1, value2) - elif op == "ne" and value1 == value2: - LOG.info("value1=%s, value2=%s, error: should not equal!!!", - value1, value2) - check_result = "FAIL" - assert value1 != value2, "Error %s==%s" % (value1, value2) LOG.info("Check result is %s", check_result) keys = self.scenario_cfg.get('output', '').split() values = [check_result] diff --git a/yardstick/benchmark/scenarios/lib/create_floating_ip.py b/yardstick/benchmark/scenarios/lib/create_floating_ip.py index 328566d48..e29f9d1fc 100644 --- a/yardstick/benchmark/scenarios/lib/create_floating_ip.py +++ b/yardstick/benchmark/scenarios/lib/create_floating_ip.py @@ -7,14 +7,13 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging import os from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions + LOG = logging.getLogger(__name__) @@ -28,8 +27,18 @@ class CreateFloatingIp(base.Scenario): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg self.ext_net_id = os.getenv("EXTERNAL_NETWORK", "external") + self.options = self.scenario_cfg["options"] + + self.network_name_or_id = self.options.get("network_name_or_id", self.ext_net_id) + self.server = self.options.get("server") + self.fixed_address = self.options.get("fixed_address") + self.nat_destination = self.options.get("nat_destination") + self.port = self.options.get("port") + self.wait = self.options.get("wait", False) + self.timeout = self.options.get("timeout", 60) + + self.shade_client = openstack_utils.get_shade_client() - self.neutron_client = op_utils.get_neutron_client() self.setup_done = False def setup(self): @@ -43,18 +52,19 @@ class CreateFloatingIp(base.Scenario): if not self.setup_done: self.setup() - net_id = op_utils.get_network_id(self.neutron_client, self.ext_net_id) - floating_info = op_utils.create_floating_ip(self.neutron_client, - extnet_id=net_id) - if floating_info: - LOG.info("Creating floating ip successful!") - else: + floating_info = openstack_utils.create_floating_ip( + self.shade_client, network_name_or_id=self.network_name_or_id, + server=self.server, fixed_address=self.fixed_address, + nat_destination=self.nat_destination, port=self.port, + wait=self.wait, timeout=self.timeout) + + if not floating_info: + result.update({"floating_ip_create": 0}) LOG.error("Creating floating ip failed!") + raise exceptions.ScenarioCreateFloatingIPError - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [floating_info["fip_id"], floating_info["fip_addr"]] - return self._push_to_outputs(keys, values) + result.update({"floating_ip_create": 1}) + LOG.info("Creating floating ip successful!") + keys = self.scenario_cfg.get("output", '').split() + values = [floating_info["fip_id"], floating_info["fip_addr"]] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_image.py b/yardstick/benchmark/scenarios/lib/create_image.py index bcffc7452..d057894a9 100644 --- a/yardstick/benchmark/scenarios/lib/create_image.py +++ b/yardstick/benchmark/scenarios/lib/create_image.py @@ -6,14 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -26,20 +23,23 @@ class CreateImage(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] - - self.image_name = self.options.get("image_name", "TestImage") - self.file_path = self.options.get("file_path", None) - self.disk_format = self.options.get("disk_format", "qcow2") - self.container_format = self.options.get("container_format", "bare") - self.min_disk = self.options.get("min_disk", 0) - self.min_ram = self.options.get("min_ram", 0) - self.protected = self.options.get("protected", False) - self.public = self.options.get("public", "public") - self.tags = self.options.get("tags", []) - self.custom_property = self.options.get("property", {}) - - self.glance_client = op_utils.get_glance_client() + self.options = self.scenario_cfg["options"] + + self.name = self.options["image_name"] + self.file_name = self.options.get("file_name") + self.container = self.options.get("container", 'images') + self.md5 = self.options.get("md5") + self.sha256 = self.options.get("sha256") + self.disk_format = self.options.get("disk_format") + self.container_format = self.options.get("container_format",) + self.disable_vendor_agent = self.options.get("disable_vendor_agent", True) + self.wait = self.options.get("wait", True) + self.timeout = self.options.get("timeout", 3600) + self.allow_duplicates = self.options.get("allow_duplicates", False) + self.meta = self.options.get("meta") + self.volume = self.options.get("volume") + + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -54,19 +54,22 @@ class CreateImage(base.Scenario): if not self.setup_done: self.setup() - image_id = op_utils.create_image(self.glance_client, self.image_name, - self.file_path, self.disk_format, - self.container_format, self.min_disk, - self.min_ram, self.protected, self.tags, - self.public, **self.custom_property) - - if image_id: - LOG.info("Create image successful!") - values = [image_id] - - else: - LOG.info("Create image failed!") - values = [] - - keys = self.scenario_cfg.get('output', '').split() + image_id = openstack_utils.create_image( + self.shade_client, self.name, filename=self.file_name, + container=self.container, md5=self.md5, sha256=self.sha256, + disk_format=self.disk_format, + container_format=self.container_format, + disable_vendor_agent=self.disable_vendor_agent, wait=self.wait, + timeout=self.timeout, allow_duplicates=self.allow_duplicates, + meta=self.meta, volume=self.volume) + + if not image_id: + result.update({"image_create": 0}) + LOG.error("Create image failed!") + raise exceptions.ScenarioCreateImageError + + result.update({"image_create": 1}) + LOG.info("Create image successful!") + keys = self.scenario_cfg.get("output", '').split() + values = [image_id] return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_keypair.py b/yardstick/benchmark/scenarios/lib/create_keypair.py index f5b1fff7a..ee9bc440a 100644 --- a/yardstick/benchmark/scenarios/lib/create_keypair.py +++ b/yardstick/benchmark/scenarios/lib/create_keypair.py @@ -6,15 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging -import paramiko from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -27,10 +23,11 @@ class CreateKeypair(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.key_name = self.options.get("key_name", "yardstick_key") - self.key_filename = self.options.get("key_path", "/tmp/yardstick_key") + self.name = self.options["key_name"] + self.public_key = self.options.get("public_key") + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,27 +42,17 @@ class CreateKeypair(base.Scenario): if not self.setup_done: self.setup() - rsa_key = paramiko.RSAKey.generate(bits=2048, progress_func=None) - rsa_key.write_private_key_file(self.key_filename) - LOG.info("Writing key_file %s ...", self.key_filename) - with open(self.key_filename + ".pub", "w") as pubkey_file: - pubkey_file.write( - "%s %s\n" % (rsa_key.get_name(), rsa_key.get_base64())) - del rsa_key - - keypair = op_utils.create_keypair(self.key_name, - self.key_filename + ".pub") + keypair = openstack_utils.create_keypair( + self.shade_client, self.name, public_key=self.public_key) - if keypair: - result.update({"keypair_create": 1}) - LOG.info("Create keypair successful!") - else: + if not keypair: result.update({"keypair_create": 0}) - LOG.info("Create keypair failed!") - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [keypair.id] - return self._push_to_outputs(keys, values) + LOG.error("Create keypair failed!") + raise exceptions.ScenarioCreateKeypairError + + result.update({"keypair_create": 1}) + LOG.info("Create keypair successful!") + keys = self.scenario_cfg.get("output", '').split() + keypair_id = keypair["id"] + values = [keypair_id] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_network.py b/yardstick/benchmark/scenarios/lib/create_network.py index cffff132a..734820519 100644 --- a/yardstick/benchmark/scenarios/lib/create_network.py +++ b/yardstick/benchmark/scenarios/lib/create_network.py @@ -7,13 +7,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions + LOG = logging.getLogger(__name__) @@ -28,9 +27,14 @@ class CreateNetwork(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - self.openstack = self.options.get("openstack_paras", None) + self.network_name = self.options["network_name"] + self.shared = self.options.get("shared", False) + self.admin_state_up = self.options.get("admin_state_up", True) + self.external = self.options.get("external", False) + self.provider = self.options.get("provider") + self.project_id = self.options.get("project_id") - self.neutron_client = op_utils.get_neutron_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,20 +49,17 @@ class CreateNetwork(base.Scenario): if not self.setup_done: self.setup() - openstack_paras = {'network': self.openstack} - network_id = op_utils.create_neutron_net(self.neutron_client, - openstack_paras) - if network_id: - result.update({"network_create": 1}) - LOG.info("Create network successful!") - else: + network_id = openstack_utils.create_neutron_net( + self.shade_client, self.network_name, shared=self.shared, + admin_state_up=self.admin_state_up, external=self.external, + provider=self.provider, project_id=self.project_id) + if not network_id: result.update({"network_create": 0}) LOG.error("Create network failed!") + raise exceptions.ScenarioCreateNetworkError - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [network_id] - return self._push_to_outputs(keys, values) + result.update({"network_create": 1}) + LOG.info("Create network successful!") + keys = self.scenario_cfg.get('output', '').split() + values = [network_id] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_router.py b/yardstick/benchmark/scenarios/lib/create_router.py index 9aa57ebb2..34252f603 100644 --- a/yardstick/benchmark/scenarios/lib/create_router.py +++ b/yardstick/benchmark/scenarios/lib/create_router.py @@ -7,13 +7,11 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -28,9 +26,14 @@ class CreateRouter(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - self.openstack = self.options.get("openstack_paras", None) + self.name = self.options.get('name') + self.admin_state_up = self.options.get('admin_state_up', True) + self.ext_gateway_net_id = self.options.get('ext_gateway_net_id') + self.enable_snat = self.options.get('enable_snat') + self.ext_fixed_ips = self.options.get('ext_fixed_ips') + self.project_id = self.options.get('project_id') - self.neutron_client = op_utils.get_neutron_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,22 +48,19 @@ class CreateRouter(base.Scenario): if not self.setup_done: self.setup() - openstack_paras = {'router': self.openstack} - router_id = op_utils.create_neutron_router(self.neutron_client, - openstack_paras) - if router_id: - result.update({"network_create": 1}) - LOG.info("Create router successful!") - else: - result.update({"network_create": 0}) + router_id = openstack_utils.create_neutron_router( + self.shade_client, name=self.name, + admin_state_up=self.admin_state_up, + ext_gateway_net_id=self.ext_gateway_net_id, + enable_snat=self.enable_snat, ext_fixed_ips=self.ext_fixed_ips, + project_id=self.project_id) + if not router_id: + result.update({"router_create": 0}) LOG.error("Create router failed!") + raise exceptions.ScenarioCreateRouterError - check_result = router_id - - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [check_result] - return self._push_to_outputs(keys, values) + result.update({"router_create": 1}) + LOG.info("Create router successful!") + keys = self.scenario_cfg.get('output', '').split() + values = [router_id] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_sec_group.py b/yardstick/benchmark/scenarios/lib/create_sec_group.py index 3d1aec9e8..1d2e36488 100644 --- a/yardstick/benchmark/scenarios/lib/create_sec_group.py +++ b/yardstick/benchmark/scenarios/lib/create_sec_group.py @@ -7,13 +7,11 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -26,11 +24,12 @@ class CreateSecgroup(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.sg_name = self.options.get("sg_name", "yardstick_sec_group") - self.description = self.options.get("description", None) - self.neutron_client = op_utils.get_neutron_client() + self.sg_name = self.options["sg_name"] + self.description = self.options.get("description", "") + self.project_id = self.options.get("project_id") + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,21 +44,16 @@ class CreateSecgroup(base.Scenario): if not self.setup_done: self.setup() - sg_id = op_utils.create_security_group_full(self.neutron_client, - sg_name=self.sg_name, - sg_description=self.description) - - if sg_id: - result.update({"sg_create": 1}) - LOG.info("Create security group successful!") - else: + sg_id = openstack_utils.create_security_group_full( + self.shade_client, self.sg_name, sg_description=self.description, + project_id=self.project_id) + if not sg_id: result.update({"sg_create": 0}) LOG.error("Create security group failed!") + raise exceptions.ScenarioCreateSecurityGroupError - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [sg_id] - return self._push_to_outputs(keys, values) + result.update({"sg_create": 1}) + LOG.info("Create security group successful!") + keys = self.scenario_cfg.get("output", '').split() + values = [sg_id] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_server.py b/yardstick/benchmark/scenarios/lib/create_server.py index 31ba18ed4..e2748aecf 100644 --- a/yardstick/benchmark/scenarios/lib/create_server.py +++ b/yardstick/benchmark/scenarios/lib/create_server.py @@ -6,14 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -26,15 +23,27 @@ class CreateServer(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] - - self.image_name = self.options.get("image_name", None) - self.flavor_name = self.options.get("flavor_name", None) - self.openstack = self.options.get("openstack_paras", None) - - self.glance_client = op_utils.get_glance_client() - self.neutron_client = op_utils.get_neutron_client() - self.nova_client = op_utils.get_nova_client() + self.options = self.scenario_cfg["options"] + + self.name = self.options["name"] + self.image = self.options["image"] + self.flavor = self.options["flavor"] + self.auto_ip = self.options.get("auto_ip", True) + self.ips = self.options.get("ips") + self.ip_pool = self.options.get("ip_pool") + self.root_volume = self.options.get("root_volume") + self.terminate_volume = self.options.get("terminate_volume", False) + self.wait = self.options.get("wait", True) + self.timeout = self.options.get("timeout", 180) + self.reuse_ips = self.options.get("reuse_ips", True) + self.network = self.options.get("network") + self.boot_from_volume = self.options.get("boot_from_volume", False) + self.volume_size = self.options.get("volume_size", "20") + self.boot_volume = self.options.get("boot_volume") + self.volumes = self.options.get("volumes") + self.nat_destination = self.options.get("nat_destination") + + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -49,26 +58,23 @@ class CreateServer(base.Scenario): if not self.setup_done: self.setup() - if self.image_name is not None: - self.openstack['image'] = op_utils.get_image_id(self.glance_client, - self.image_name) - if self.flavor_name is not None: - self.openstack['flavor'] = op_utils.get_flavor_id(self.nova_client, - self.flavor_name) - - vm = op_utils.create_instance_and_wait_for_active(self.openstack) - - if vm: - result.update({"instance_create": 1}) - LOG.info("Create server successful!") - else: + server = openstack_utils.create_instance_and_wait_for_active( + self.shade_client, self.name, self.image, + self.flavor, auto_ip=self.auto_ip, ips=self.ips, + ip_pool=self.ip_pool, root_volume=self.root_volume, + terminate_volume=self.terminate_volume, wait=self.wait, + timeout=self.timeout, reuse_ips=self.reuse_ips, + network=self.network, boot_from_volume=self.boot_from_volume, + volume_size=self.volume_size, boot_volume=self.boot_volume, + volumes=self.volumes, nat_destination=self.nat_destination) + + if not server: result.update({"instance_create": 0}) LOG.error("Create server failed!") + raise exceptions.ScenarioCreateServerError - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [vm.id] - return self._push_to_outputs(keys, values) + result.update({"instance_create": 1}) + LOG.info("Create instance successful!") + keys = self.scenario_cfg.get("output", '').split() + values = [server["id"]] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_subnet.py b/yardstick/benchmark/scenarios/lib/create_subnet.py index c34af8a9e..e383c99de 100644 --- a/yardstick/benchmark/scenarios/lib/create_subnet.py +++ b/yardstick/benchmark/scenarios/lib/create_subnet.py @@ -7,13 +7,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions + LOG = logging.getLogger(__name__) @@ -28,9 +27,23 @@ class CreateSubnet(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - self.openstack = self.options.get("openstack_paras", None) - - self.neutron_client = op_utils.get_neutron_client() + self.network_name_or_id = self.options['network_name_or_id'] + self.cidr = self.options.get('cidr') + self.ip_version = self.options.get('ip_version', 4) + self.enable_dhcp = self.options.get('enable_dhcp', False) + self.subnet_name = self.options.get('subnet_name') + self.tenant_id = self.options.get('tenant_id') + self.allocation_pools = self.options.get('allocation_pools') + self.gateway_ip = self.options.get('gateway_ip') + self.disable_gateway_ip = self.options.get('disable_gateway_ip', False) + self.dns_nameservers = self.options.get('dns_nameservers') + self.host_routes = self.options.get('host_routes') + self.ipv6_ra_mode = self.options.get('ipv6_ra_mode') + self.ipv6_address_mode = self.options.get('ipv6_address_mode') + self.use_default_subnetpool = self.options.get( + 'use_default_subnetpool', False) + + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,22 +58,23 @@ class CreateSubnet(base.Scenario): if not self.setup_done: self.setup() - openstack_paras = {'subnets': [self.openstack]} - subnet_id = op_utils.create_neutron_subnet(self.neutron_client, - openstack_paras) - if subnet_id: - result.update({"subnet_create": 1}) - LOG.info("Create subnet successful!") - else: + subnet_id = openstack_utils.create_neutron_subnet( + self.shade_client, self.network_name_or_id, cidr=self.cidr, + ip_version=self.ip_version, enable_dhcp=self.enable_dhcp, + subnet_name=self.subnet_name, tenant_id=self.tenant_id, + allocation_pools=self.allocation_pools, gateway_ip=self.gateway_ip, + disable_gateway_ip=self.disable_gateway_ip, + dns_nameservers=self.dns_nameservers, host_routes=self.host_routes, + ipv6_ra_mode=self.ipv6_ra_mode, + ipv6_address_mode=self.ipv6_address_mode, + use_default_subnetpool=self.use_default_subnetpool) + if not subnet_id: result.update({"subnet_create": 0}) LOG.error("Create subnet failed!") + raise exceptions.ScenarioCreateSubnetError - check_result = subnet_id - - try: - keys = self.scenario_cfg.get('output', '').split() - except KeyError: - pass - else: - values = [check_result] - return self._push_to_outputs(keys, values) + result.update({"subnet_create": 1}) + LOG.info("Create subnet successful!") + keys = self.scenario_cfg.get('output', '').split() + values = [subnet_id] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/create_volume.py b/yardstick/benchmark/scenarios/lib/create_volume.py index c7086d0ef..b66749026 100644 --- a/yardstick/benchmark/scenarios/lib/create_volume.py +++ b/yardstick/benchmark/scenarios/lib/create_volume.py @@ -7,14 +7,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import time import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -27,15 +25,16 @@ class CreateVolume(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.volume_name = self.options.get("volume_name", "TestVolume") - self.volume_size = self.options.get("size", 100) - self.image_name = self.options.get("image", None) - self.image_id = None + self.size = self.options["size_gb"] + self.wait = self.options.get("wait", True) + self.timeout = self.options.get("timeout") + self.image = self.options.get("image") + self.name = self.options.get("name") + self.description = self.options.get("description") - self.glance_client = op_utils.get_glance_client() - self.cinder_client = op_utils.get_cinder_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -50,22 +49,23 @@ class CreateVolume(base.Scenario): if not self.setup_done: self.setup() - if self.image_name: - self.image_id = op_utils.get_image_id(self.glance_client, - self.image_name) + volume = openstack_utils.create_volume( + self.shade_client, self.size, wait=self.wait, timeout=self.timeout, + image=self.image, name=self.name, description=self.description) - volume = op_utils.create_volume(self.cinder_client, self.volume_name, - self.volume_size, self.image_id) + if not volume: + result.update({"volume_create": 0}) + LOG.error("Create volume failed!") + raise exceptions.ScenarioCreateVolumeError - status = volume.status - while(status == 'creating' or status == 'downloading'): - LOG.info("Volume status is: %s" % status) + status = volume["status"] + while status == "creating" or status == "downloading": + LOG.info("Volume status is: %s", status) time.sleep(5) - volume = op_utils.get_volume_by_name(self.volume_name) - status = volume.status - + volume = openstack_utils.get_volume(self.shade_client, self.name) + status = volume["status"] + result.update({"volume_create": 1}) LOG.info("Create volume successful!") - - values = [volume.id] - keys = self.scenario_cfg.get('output', '').split() + values = [volume["id"]] + keys = self.scenario_cfg.get("output", '').split() return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/delete_floating_ip.py b/yardstick/benchmark/scenarios/lib/delete_floating_ip.py index 4314952fb..a35445f6f 100644 --- a/yardstick/benchmark/scenarios/lib/delete_floating_ip.py +++ b/yardstick/benchmark/scenarios/lib/delete_floating_ip.py @@ -7,13 +7,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging +from yardstick.common import openstack_utils +from yardstick.common import exceptions from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils + LOG = logging.getLogger(__name__) @@ -28,9 +27,10 @@ class DeleteFloatingIp(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - self.floating_ip_id = self.options.get("floating_ip_id", None) + self.floating_ip_id = self.options["floating_ip_id"] + self.retry = self.options.get("retry", 1) - self.nova_client = op_utils.get_nova_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False def setup(self): @@ -44,11 +44,13 @@ class DeleteFloatingIp(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.delete_floating_ip(nova_client=self.nova_client, - floatingip_id=self.floating_ip_id) - if status: - result.update({"delete_floating_ip": 1}) - LOG.info("Delete floating ip successful!") - else: + status = openstack_utils.delete_floating_ip( + self.shade_client, self.floating_ip_id, + retry=self.retry) + if not status: result.update({"delete_floating_ip": 0}) LOG.error("Delete floating ip failed!") + raise exceptions.ScenarioDeleteFloatingIPError + + result.update({"delete_floating_ip": 1}) + LOG.info("Delete floating ip successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_image.py b/yardstick/benchmark/scenarios/lib/delete_image.py index 0e3a853e5..008f104b2 100644 --- a/yardstick/benchmark/scenarios/lib/delete_image.py +++ b/yardstick/benchmark/scenarios/lib/delete_image.py @@ -7,13 +7,11 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -26,12 +24,14 @@ class DeleteImage(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.image_name = self.options.get("image_name", "TestImage") - self.image_id = None + self.image_name_or_id = self.options["name_or_id"] + self.wait = self.options.get("wait", False) + self.timeout = self.options.get("timeout", 3600) + self.delete_objects = self.options.get("delete_objects", True) - self.glance_client = op_utils.get_glance_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -46,16 +46,14 @@ class DeleteImage(base.Scenario): if not self.setup_done: self.setup() - self.image_id = op_utils.get_image_id(self.glance_client, self.image_name) - LOG.info("Deleting image: %s", self.image_name) - status = op_utils.delete_image(self.glance_client, self.image_id) + status = openstack_utils.delete_image( + self.shade_client, self.image_name_or_id, wait=self.wait, + timeout=self.timeout, delete_objects=self.delete_objects) - if status: - LOG.info("Delete image successful!") - values = [status] - else: - LOG.info("Delete image failed!") - values = [] + if not status: + result.update({"delete_image": 0}) + LOG.error("Delete image failed!") + raise exceptions.ScenarioDeleteImageError - keys = self.scenario_cfg.get('output', '').split() - return self._push_to_outputs(keys, values) + result.update({"delete_image": 1}) + LOG.info("Delete image successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_keypair.py b/yardstick/benchmark/scenarios/lib/delete_keypair.py index 135139959..a52a38567 100644 --- a/yardstick/benchmark/scenarios/lib/delete_keypair.py +++ b/yardstick/benchmark/scenarios/lib/delete_keypair.py @@ -6,14 +6,12 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging +from yardstick.common import openstack_utils +from yardstick.common import exceptions from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils + LOG = logging.getLogger(__name__) @@ -26,11 +24,11 @@ class DeleteKeypair(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.key_name = self.options.get("key_name", "yardstick_key") + self.key_name = self.options["key_name"] - self.nova_client = op_utils.get_nova_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,12 +43,13 @@ class DeleteKeypair(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.delete_keypair(self.nova_client, - self.key_name) + status = openstack_utils.delete_keypair(self.shade_client, + self.key_name) - if status: - result.update({"delete_keypair": 1}) - LOG.info("Delete keypair successful!") - else: + if not status: result.update({"delete_keypair": 0}) - LOG.info("Delete keypair failed!") + LOG.error("Delete keypair failed!") + raise exceptions.ScenarioDeleteKeypairError + + result.update({"delete_keypair": 1}) + LOG.info("Delete keypair successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_network.py b/yardstick/benchmark/scenarios/lib/delete_network.py index e8796bf82..8874e8b1e 100644 --- a/yardstick/benchmark/scenarios/lib/delete_network.py +++ b/yardstick/benchmark/scenarios/lib/delete_network.py @@ -7,13 +7,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions + LOG = logging.getLogger(__name__) @@ -26,11 +25,11 @@ class DeleteNetwork(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.network_id = self.options.get("network_id", None) + self.network_name_or_id = self.options["network_name_or_id"] - self.neutron_client = op_utils.get_neutron_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,11 +44,13 @@ class DeleteNetwork(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.delete_neutron_net(self.neutron_client, - network_id=self.network_id) - if status: - result.update({"delete_network": 1}) - LOG.info("Delete network successful!") - else: + status = openstack_utils.delete_neutron_net(self.shade_client, + self.network_name_or_id) + + if not status: result.update({"delete_network": 0}) LOG.error("Delete network failed!") + raise exceptions.ScenarioDeleteNetworkError + + result.update({"delete_network": 1}) + LOG.info("Delete network successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_router.py b/yardstick/benchmark/scenarios/lib/delete_router.py index 358fd40cf..5e7467b2c 100644 --- a/yardstick/benchmark/scenarios/lib/delete_router.py +++ b/yardstick/benchmark/scenarios/lib/delete_router.py @@ -7,13 +7,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging +from yardstick.common import openstack_utils +from yardstick.common import exceptions from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils + LOG = logging.getLogger(__name__) @@ -28,9 +27,9 @@ class DeleteRouter(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - self.router_id = self.options.get("router_id", None) + self.router_id = self.options["router_id"] - self.neutron_client = op_utils.get_neutron_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,11 +44,12 @@ class DeleteRouter(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.delete_neutron_router(self.neutron_client, - router_id=self.router_id) - if status: - result.update({"delete_router": 1}) - LOG.info("Delete router successful!") - else: + status = openstack_utils.delete_neutron_router(self.shade_client, + self.router_id) + if not status: result.update({"delete_router": 0}) LOG.error("Delete router failed!") + raise exceptions.ScenarioDeleteRouterError + + result.update({"delete_router": 1}) + LOG.info("Delete router successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_router_interface.py b/yardstick/benchmark/scenarios/lib/delete_router_interface.py index 117c80811..e71aed3ef 100644 --- a/yardstick/benchmark/scenarios/lib/delete_router_interface.py +++ b/yardstick/benchmark/scenarios/lib/delete_router_interface.py @@ -7,13 +7,11 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -28,10 +26,11 @@ class DeleteRouterInterface(base.Scenario): self.context_cfg = context_cfg self.options = self.scenario_cfg['options'] - self.subnet_id = self.options.get("subnet_id", None) - self.router_id = self.options.get("router_id", None) + self.router = self.options["router"] + self.subnet_id = self.options.get("subnet_id") + self.port_id = self.options.get("port_id") - self.neutron_client = op_utils.get_neutron_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -46,12 +45,13 @@ class DeleteRouterInterface(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.remove_interface_router(self.neutron_client, - router_id=self.router_id, - subnet_id=self.subnet_id) - if status: - result.update({"delete_router_interface": 1}) - LOG.info("Delete router interface successful!") - else: + status = openstack_utils.remove_router_interface( + self.shade_client, self.router, subnet_id=self.subnet_id, + port_id=self.port_id) + if not status: result.update({"delete_router_interface": 0}) LOG.error("Delete router interface failed!") + raise exceptions.ScenarioRemoveRouterIntError + + result.update({"delete_router_interface": 1}) + LOG.info("Delete router interface successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_server.py b/yardstick/benchmark/scenarios/lib/delete_server.py index bcd8faba7..46229ff04 100644 --- a/yardstick/benchmark/scenarios/lib/delete_server.py +++ b/yardstick/benchmark/scenarios/lib/delete_server.py @@ -6,14 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging +from yardstick.common import openstack_utils +from yardstick.common import exceptions from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils LOG = logging.getLogger(__name__) @@ -26,9 +23,13 @@ class DeleteServer(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] - self.server_id = self.options.get("server_id", None) - self.nova_client = op_utils.get_nova_client() + self.options = self.scenario_cfg["options"] + self.server_name_or_id = self.options["name_or_id"] + self.wait = self.options.get("wait", False) + self.timeout = self.options.get("timeout", 180) + self.delete_ips = self.options.get("delete_ips", False) + self.delete_ip_retry = self.options.get("delete_ip_retry", 1) + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -43,9 +44,15 @@ class DeleteServer(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.delete_instance(self.nova_client, - instance_id=self.server_id) - if status: - LOG.info("Delete server successful!") - else: + status = openstack_utils.delete_instance( + self.shade_client, self.server_name_or_id, wait=self.wait, + timeout=self.timeout, delete_ips=self.delete_ips, + delete_ip_retry=self.delete_ip_retry) + + if not status: + result.update({"delete_server": 0}) LOG.error("Delete server failed!") + raise exceptions.ScenarioDeleteServerError + + result.update({"delete_server": 1}) + LOG.info("Delete server successful!") diff --git a/yardstick/benchmark/scenarios/lib/delete_volume.py b/yardstick/benchmark/scenarios/lib/delete_volume.py index ea2b85812..59e19dfdf 100644 --- a/yardstick/benchmark/scenarios/lib/delete_volume.py +++ b/yardstick/benchmark/scenarios/lib/delete_volume.py @@ -6,14 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging +from yardstick.common import openstack_utils +from yardstick.common import exceptions from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils LOG = logging.getLogger(__name__) @@ -26,11 +23,13 @@ class DeleteVolume(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.volume_id = self.options.get("volume_id", None) + self.volume_name_or_id = self.options.get("name_or_id") + self.wait = self.options.get("wait", True) + self.timeout = self.options.get("timeout") - self.cinder_client = op_utils.get_cinder_client() + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -45,11 +44,14 @@ class DeleteVolume(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.delete_volume(self.cinder_client, self.volume_id) + status = openstack_utils.delete_volume( + self.shade_client, name_or_id=self.volume_name_or_id, + wait=self.wait, timeout=self.timeout) - if status: - result.update({"delete_volume": 1}) - LOG.info("Delete volume successful!") - else: + if not status: result.update({"delete_volume": 0}) - LOG.info("Delete volume failed!") + LOG.error("Delete volume failed!") + raise exceptions.ScenarioDeleteVolumeError + + result.update({"delete_volume": 1}) + LOG.info("Delete volume successful!") diff --git a/yardstick/benchmark/scenarios/lib/detach_volume.py b/yardstick/benchmark/scenarios/lib/detach_volume.py index 0b02a3a81..76c0167bd 100644 --- a/yardstick/benchmark/scenarios/lib/detach_volume.py +++ b/yardstick/benchmark/scenarios/lib/detach_volume.py @@ -6,14 +6,12 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging +from yardstick.common import openstack_utils +from yardstick.common import exceptions from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils + LOG = logging.getLogger(__name__) @@ -26,10 +24,14 @@ class DetachVolume(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] + self.options = self.scenario_cfg["options"] - self.server_id = self.options.get("server_id", "TestServer") - self.volume_id = self.options.get("volume_id", None) + self.server = self.options["server_name_or_id"] + self.volume = self.options["volume_name_or_id"] + self.wait = self.options.get("wait", True) + self.timeout = self.options.get("timeout") + + self.shade_client = openstack_utils.get_shade_client() self.setup_done = False @@ -44,11 +46,14 @@ class DetachVolume(base.Scenario): if not self.setup_done: self.setup() - status = op_utils.detach_volume(self.server_id, self.volume_id) + status = openstack_utils.detach_volume( + self.shade_client, self.server, self.volume, + wait=self.wait, timeout=self.timeout) - if status: - result.update({"detach_volume": 1}) - LOG.info("Detach volume from server successful!") - else: + if not status: result.update({"detach_volume": 0}) - LOG.info("Detach volume from server failed!") + LOG.error("Detach volume from server failed!") + raise exceptions.ScenarioDetachVolumeError + + result.update({"detach_volume": 1}) + LOG.info("Detach volume from server successful!") diff --git a/yardstick/benchmark/scenarios/lib/get_flavor.py b/yardstick/benchmark/scenarios/lib/get_flavor.py index d5e33947e..6727a7343 100644 --- a/yardstick/benchmark/scenarios/lib/get_flavor.py +++ b/yardstick/benchmark/scenarios/lib/get_flavor.py @@ -6,14 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -26,8 +23,12 @@ class GetFlavor(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg['options'] - self.flavor_name = self.options.get("flavor_name", "TestFlavor") + self.options = self.scenario_cfg["options"] + self.name_or_id = self.options["name_or_id"] + self.filters = self.options.get("filters") + self.get_extra = self.options.get("get_extra", True) + self.shade_client = openstack_utils.get_shade_client() + self.setup_done = False def setup(self): @@ -41,14 +42,18 @@ class GetFlavor(base.Scenario): if not self.setup_done: self.setup() - LOG.info("Querying flavor: %s", self.flavor_name) - flavor = op_utils.get_flavor_by_name(self.flavor_name) - if flavor: - LOG.info("Get flavor successful!") - values = [self._change_obj_to_dict(flavor)] - else: - LOG.info("Get flavor: no flavor matched!") - values = [] + LOG.info("Querying flavor: %s", self.name_or_id) + flavor = openstack_utils.get_flavor( + self.shade_client, self.name_or_id, filters=self.filters, + get_extra=self.get_extra) + + if not flavor: + result.update({"get_flavor": 0}) + LOG.error("Get flavor failed!") + raise exceptions.ScenarioGetFlavorError - keys = self.scenario_cfg.get('output', '').split() + result.update({"get_flavor": 1}) + LOG.info("Get flavor successful!") + values = [flavor] + keys = self.scenario_cfg.get("output", '').split() return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/get_server.py b/yardstick/benchmark/scenarios/lib/get_server.py index fcf47c80d..f65fa9ebf 100644 --- a/yardstick/benchmark/scenarios/lib/get_server.py +++ b/yardstick/benchmark/scenarios/lib/get_server.py @@ -6,14 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - -from __future__ import print_function -from __future__ import absolute_import - import logging from yardstick.benchmark.scenarios import base -import yardstick.common.openstack_utils as op_utils +from yardstick.common import openstack_utils +from yardstick.common import exceptions LOG = logging.getLogger(__name__) @@ -21,63 +18,58 @@ LOG = logging.getLogger(__name__) class GetServer(base.Scenario): """Get a server instance - Parameters - server_id - ID of the server - type: string - unit: N/A - default: null - server_name - name of the server - type: string - unit: N/A - default: null - - Either server_id or server_name is required. - - Outputs + Parameters: + name_or_id - Name or ID of the server + type: string + filters - meta data to use for further filtering + type: dict + detailed: Whether or not to add detailed additional information. + type: bool + bare: Whether to skip adding any additional information to the server + record. + type: bool + all_projects: Whether to get server from all projects or just the current + auth scoped project. + type: bool + + Outputs: rc - response code of getting server instance - 0 for success - 1 for failure + 1 for success + 0 for failure type: int - unit: N/A server - instance of the server type: dict - unit: N/A + """ - __scenario_type__ = "GetServer" + __scenario_type__ = 'GetServer' def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - self.options = self.scenario_cfg.get('options', {}) + self.options = self.scenario_cfg['options'] - self.server_id = self.options.get("server_id") - if self.server_id: - LOG.debug('Server id is %s', self.server_id) + self.server_name_or_id = self.options.get('name_or_id') + self.filters = self.options.get('filters') + self.detailed = self.options.get('detailed', False) + self.bare = self.options.get('bare', False) - default_name = self.scenario_cfg.get('host', - self.scenario_cfg.get('target')) - self.server_name = self.options.get('server_name', default_name) - if self.server_name: - LOG.debug('Server name is %s', self.server_name) - - self.nova_client = op_utils.get_nova_client() + self.shade_client = openstack_utils.get_shade_client() def run(self, result): """execute the test""" - if self.server_id: - server = self.nova_client.servers.get(self.server_id) - else: - server = op_utils.get_server_by_name(self.server_name) - - keys = self.scenario_cfg.get('output', '').split() + server = openstack_utils.get_server( + self.shade_client, name_or_id=self.server_name_or_id, + filters=self.filters, detailed=self.detailed, bare=self.bare) - if server: - LOG.info("Get server successful!") - values = [0, self._change_obj_to_dict(server)] - else: - LOG.info("Get server failed!") - values = [1] + if not server: + result.update({'get_server': 0}) + LOG.error('Get Server failed!') + raise exceptions.ScenarioGetServerError + result.update({'get_server': 1}) + LOG.info('Get Server successful!') + keys = self.scenario_cfg.get('output', '').split() + values = [server] return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/networking/iperf3.py b/yardstick/benchmark/scenarios/networking/iperf3.py index 98c45990e..51e044e7b 100644 --- a/yardstick/benchmark/scenarios/networking/iperf3.py +++ b/yardstick/benchmark/scenarios/networking/iperf3.py @@ -92,7 +92,7 @@ For more info see http://software.es.net/iperf def teardown(self): LOG.debug("teardown") self.host.close() - status, stdout, stderr = self.target.execute("pkill iperf3") + status, _, stderr = self.target.execute("pkill iperf3") if status: LOG.warning(stderr) self.target.close() @@ -145,7 +145,7 @@ For more info see http://software.es.net/iperf LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.host.execute(cmd) + status, stdout, _ = self.host.execute(cmd) if status: # error cause in json dict on stdout raise RuntimeError(stdout) @@ -165,16 +165,17 @@ For more info see http://software.es.net/iperf bit_per_second = \ int(iperf_result["end"]["sum_received"]["bits_per_second"]) bytes_per_second = bit_per_second / 8 - assert bytes_per_second >= sla_bytes_per_second, \ - "bytes_per_second %d < sla:bytes_per_second (%d); " % \ - (bytes_per_second, sla_bytes_per_second) + self.verify_SLA( + bytes_per_second >= sla_bytes_per_second, + "bytes_per_second %d < sla:bytes_per_second (%d); " + % (bytes_per_second, sla_bytes_per_second)) else: sla_jitter = float(sla_iperf["jitter"]) jitter_ms = float(iperf_result["end"]["sum"]["jitter_ms"]) - assert jitter_ms <= sla_jitter, \ - "jitter_ms %f > sla:jitter %f; " % \ - (jitter_ms, sla_jitter) + self.verify_SLA(jitter_ms <= sla_jitter, + "jitter_ms %f > sla:jitter %f; " + % (jitter_ms, sla_jitter)) def _test(): diff --git a/yardstick/benchmark/scenarios/networking/moongen_testpmd.bash b/yardstick/benchmark/scenarios/networking/moongen_testpmd.bash new file mode 100644 index 000000000..3e92cc900 --- /dev/null +++ b/yardstick/benchmark/scenarios/networking/moongen_testpmd.bash @@ -0,0 +1,62 @@ +############################################################################## +# Copyright (c) 2018 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 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +#!/bin/bash + +set -e + +# Commandline arguments +MOONGEN_PORT1_MAC=$1 # MAC address of the peer port +MOONGEN_PORT2_MAC=$2 # MAC address of the peer port +TESTPMD_QUEUE=$3 + +BIND_ROOT='/opt/nsb_bin' +DRIVER_ROOT='/opt/tempT/dpdk-17.02/' + +load_modules() +{ + if ! lsmod | grep "uio" &> /dev/null; then + modprobe uio + fi + + if ! lsmod | grep "igb_uio" &> /dev/null; then + insmod ${DRIVER_ROOT}/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko + fi + + if ! lsmod | grep "rte_kni" &> /dev/null; then + insmod ${DRIVER_ROOT}/x86_64-native-linuxapp-gcc/kmod/rte_kni.ko + fi +} + +change_permissions() +{ + chmod 777 /sys/bus/pci/drivers/virtio-pci/* + chmod 777 /sys/bus/pci/drivers/igb_uio/* +} + +add_interface_to_dpdk(){ + interfaces=$(lspci |grep Eth |tail -n +2 |awk '{print $1}') + ${BIND_ROOT}/dpdk_nic_bind.py --bind=igb_uio $interfaces &> /dev/null +} + +run_testpmd() +{ + blacklist=$(lspci |grep Eth |awk '{print $1}'|head -1) + cd ${DRIVER_ROOT} + sudo ./x86_64-native-linuxapp-gcc/app/testpmd -c 0x3f -n 4 -b $blacklist -- -a --nb-cores=4 --coremask=0x3c --burst=64 --txd=4096 --rxd=4096 --rxq=$TESTPMD_QUEUE --txq=$TESTPMD_QUEUE --rss-udp --eth-peer=0,$MOONGEN_PORT1_MAC --eth-peer=1,$MOONGEN_PORT2_MAC --forward-mode=mac +} + +main() +{ + load_modules + change_permissions + add_interface_to_dpdk + run_testpmd +} + +main diff --git a/yardstick/benchmark/scenarios/networking/moongen_testpmd.py b/yardstick/benchmark/scenarios/networking/moongen_testpmd.py new file mode 100644 index 000000000..e3bd7af46 --- /dev/null +++ b/yardstick/benchmark/scenarios/networking/moongen_testpmd.py @@ -0,0 +1,379 @@ +# Copyright (c) 2018 Huawei Technologies Co.,Ltd and others. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" VsperfDPDK specific scenario definition """ + +from __future__ import absolute_import +import pkg_resources +import logging +import subprocess +import time +import re +from oslo_serialization import jsonutils + +import yardstick.ssh as ssh +import yardstick.common.utils as utils +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class MoongenTestPMD(base.Scenario): + """Execute vsperf with defined parameters + + Parameters: + frame_size - a frame size for which test should be executed; + Multiple frame sizes can be tested by modification of sequence runner + section inside TC YAML definition. + type: string + default: "64" + multistream - the number of simulated streams + type: string + default: 0 (disabled) + testpmd_queue - specifies how many queues you will use the VM + only useful when forward_type is true. + type: int + default: 1(one queue) + trafficgen_port1 - specifies device name of 1st interface connected to + the trafficgen + type: string + default: NA + trafficgen_port2 - specifies device name of 2nd interface connected to + the trafficgen + type: string + default: NA + moongen_host_user - specifies moongen host ssh user name + type: string + default: root + moongen_host_passwd - specifies moongen host ssh user password + type: string + default: root + moongen_host_ip - specifies moongen host ssh ip address + type: string + default NA + moongen_dir - specifies where is the moongen installtion dir + type: string + default NA + moongen_runBidirec - specifies moongen will run in one traffic + or two traffic. + type: string + default true + Package_Loss - specifies the package_Loss number in moongen server. + type: int + default 0(0%) + SearchRuntime - specifies the SearchRuntime and validation time + on moongen server. + type: int + default 60(s) + moongen_port1_mac - moongen server port1 mac address. + type: string + default NA + moongen_port2_mac - moongen server port2 mac address. + type: string + default NA + forward_type - VM forward type is l2fwd or testpmd. + type: string + default: testpmd + """ + __scenario_type__ = "MoongenTestPMD" + + TESTPMD_SCRIPT = 'moongen_testpmd.bash' + VSPERF_CONFIG = '/tmp/opnfv-vsperf-cfg.lua' + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.forward_setup_done = False + self.options = scenario_cfg.get('options', {}) + self.moongen_host_user = \ + self.options.get('moongen_host_user', "root") + self.moongen_host_passwd = \ + self.options.get('moongen_host_passwd', "r00t") + self.moongen_dir = \ + self.options.get('moongen_dir', '~/moongen.py') + self.testpmd_queue = \ + self.options.get('testpmd_queue', 1) + self.moongen_host_ip = \ + self.options.get('moongen_host_ip', "127.0.0.1") + self.moongen_port1_mac = \ + self.options.get('moongen_port1_mac', None) + self.moongen_port2_mac = \ + self.options.get('moongen_port2_mac', None) + self.tg_port1 = \ + self.options.get('trafficgen_port1', "enp2s0f0") + self.tg_port2 = \ + self.options.get('trafficgen_port2', "enp2s0f1") + self.forward_type = \ + self.options.get('forward_type', 'testpmd') + self.tgen_port1_mac = None + self.tgen_port2_mac = None + + def setup(self): + """scenario setup""" + host = self.context_cfg['host'] + + task_id = self.scenario_cfg['task_id'] + context_number = task_id.split('-')[0] + self.tg_port1_nw = 'demo' + \ + "-" + context_number + "-" + \ + self.options.get('trafficgen_port1_nw', 'test2') + self.tg_port2_nw = 'demo' + \ + "-" + context_number + "-" + \ + self.options.get('trafficgen_port2_nw', 'test3') + + # copy vsperf conf to VM + self.client = ssh.SSH.from_node(host, defaults={"user": "ubuntu"}) + # traffic generation could last long + self.client.wait(timeout=1800) + + self.server = ssh.SSH( + self.moongen_host_user, + self.moongen_host_ip, + password=self.moongen_host_passwd + ) + # traffic generation could last long + self.server.wait(timeout=1800) + + self.setup_done = True + + def forward_setup(self): + """forward tool setup""" + + # setup forward loopback in VM + self.testpmd_script = pkg_resources.resource_filename( + 'yardstick.benchmark.scenarios.networking', + self.TESTPMD_SCRIPT) + + self.client._put_file_shell(self.testpmd_script, + '~/testpmd_vsperf.sh') + + # disable Address Space Layout Randomization (ASLR) + cmd = "echo 0 | sudo tee /proc/sys/kernel/randomize_va_space" + self.client.send_command(cmd) + + if not self._is_forward_setup(): + self.tgen_port1_ip = \ + utils.get_port_ip(self.client, self.tg_port1) + self.tgen_port1_mac = \ + utils.get_port_mac(self.client, self.tg_port1) + self.client.run("tee ~/.testpmd.ipaddr.port1 > /dev/null", + stdin=self.tgen_port1_ip) + self.client.run("tee ~/.testpmd.macaddr.port1 > /dev/null", + stdin=self.tgen_port1_mac) + self.tgen_port2_ip = \ + utils.get_port_ip(self.client, self.tg_port2) + self.tgen_port2_mac = \ + utils.get_port_mac(self.client, self.tg_port2) + self.client.run("tee ~/.testpmd.ipaddr.port2 > /dev/null", + stdin=self.tgen_port2_ip) + self.client.run("tee ~/.testpmd.macaddr.port2 > /dev/null", + stdin=self.tgen_port2_mac) + else: + cmd = "cat ~/.testpmd.macaddr.port1" + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + self.tgen_port1_mac = stdout + cmd = "cat ~/.testpmd.ipaddr.port1" + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + self.tgen_port1_ip = stdout + cmd = "cat ~/.testpmd.macaddr.port2" + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + self.tgen_port2_mac = stdout + cmd = "cat ~/.testpmd.ipaddr.port2" + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + self.tgen_port2_ip = stdout + + LOG.info("forward type is %s", self.forward_type) + if self.forward_type == 'testpmd': + cmd = "sudo ip link set %s down" % (self.tg_port1) + LOG.debug("Executing command: %s", cmd) + self.client.execute(cmd) + cmd = "sudo ip link set %s down" % (self.tg_port2) + LOG.debug("Executing command: %s", cmd) + self.client.execute(cmd) + cmd = "screen -d -m sudo -E bash ~/testpmd_vsperf.sh %s %s %d" % \ + (self.moongen_port1_mac, self.moongen_port2_mac, + self.testpmd_queue) + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + elif self.forward_type == 'l2fwd': + cmd = ('sed -i "s/static char *net1 = \\\"eth1\\\";' + '/static char *net1 = \\\"%s %s %s\\\";/g" /home/l2fwd/l2fwd.c' + % (self.tg_port1, self.tgen_port1_ip, self.moongen_port1_mac)) + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.client.execute(cmd) + + cmd = ('sed -i "s/static char *net2 = \\\"eth2\\\";' + '/static char *net2 = \\\"%s %s %s\\\";/g" /home/l2fwd/l2fwd.c' + % (self.tg_port2, self.tgen_port2_ip, self.moongen_port2_mac)) + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.client.execute(cmd) + + cmd = ('cd /home/l2fwd/;make;./gen_debian_package.sh;' + 'sudo dpkg -i *.deb;' + 'sudo modprobe l2fwd') + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.client.execute(cmd) + + time.sleep(1) + + self.forward_setup_done = True + + def _is_forward_setup(self): + """Is forward already setup in the host?""" + if self.forward_type is 'testpmd': + is_run = True + cmd = "ip a | grep %s 2>/dev/null" % (self.tg_port1) + LOG.debug("Executing command: %s", cmd) + _, stdout, _ = self.client.execute(cmd) + if stdout: + is_run = False + return is_run + elif self.forward_type is 'l2fwd': + cmd = ('sudo lsmod |grep l2fwd') + LOG.debug("Executing command: %s", cmd) + _, stdout, _ = self.client.execute(cmd) + if stdout: + return True + else: + return False + + def generate_config_file(self, frame_size, multistream, + runBidirec, tg_port1_vlan, tg_port2_vlan, + SearchRuntime, Package_Loss): + out_text = """\ +VSPERF { +testType = 'throughput', +nrFlows = %d, +runBidirec = %s, +frameSize = %d, +srcMacs = {\'%s\', \'%s\'}, +dstMacs = {\'%s\', \'%s\'}, +vlanIds = {%d, %d}, +searchRunTime = %d, +validationRunTime = %d, +acceptableLossPct = %d, +ports = {0,1}, +} +""" % (multistream, runBidirec, frame_size, self.moongen_port1_mac, + self.moongen_port2_mac, self.tgen_port1_mac, self.tgen_port2_mac, + tg_port1_vlan, tg_port2_vlan, SearchRuntime, SearchRuntime, Package_Loss) + with open(self.VSPERF_CONFIG, "wt") as out_file: + out_file.write(out_text) + self.CONFIG_FILE = True + + def result_to_data(self, result): + search_pattern = re.compile( + r'\[REPORT\]\s+total\:\s+' + r'Tx\s+frames\:\s+(\d+)\s+' + r'Rx\s+Frames\:\s+(\d+)\s+' + r'frame\s+loss\:\s+(\d+)\,' + r'\s+(\d+\.\d+|\d+)%\s+' + r'Tx\s+Mpps\:\s+(\d+.\d+|\d+)\s+' + r'Rx\s+Mpps\:\s+(\d+\.\d+|\d+)', + re.IGNORECASE) + results_match = search_pattern.search(result) + if results_match: + rx_mpps = float(results_match.group(6)) + tx_mpps = float(results_match.group(5)) + else: + rx_mpps = 0 + tx_mpps = 0 + test_result = {"rx_mpps": rx_mpps, "tx_mpps": tx_mpps} + self.TO_DATA = True + return test_result + + def run(self, result): + """ execute the vsperf benchmark and return test results + within result dictionary + """ + + if not self.setup_done: + self.setup() + + # get vsperf options + multistream = self.options.get("multistream", 1) + + if not self.forward_setup_done: + self.forward_setup() + + if 'frame_size' in self.options: + frame_size = self.options.get("frame_size", 64) + Package_Loss = self.options.get("Package_Loss", 0) + runBidirec = self.options.get("moongen_runBidirec", + "true") + SearchRuntime = self.options.get("SearchRuntime", 10) + + cmd = "openstack network show %s --format json -c " \ + "provider:segmentation_id" % (self.tg_port1_nw) + LOG.debug("Executing command: %s", cmd) + output = subprocess.check_output(cmd, shell=True) + try: + tg_port1_vlan = jsonutils.loads(output).get("provider:segmentation_id", 1) + except TypeError: + tg_port1_vlan = 1 + + cmd = "openstack network show %s --format json -c " \ + "provider:segmentation_id" % (self.tg_port2_nw) + LOG.debug("Executing command: %s", cmd) + output = subprocess.check_output(cmd, shell=True) + try: + tg_port2_vlan = jsonutils.loads(output).get("provider:segmentation_id", 2) + except TypeError: + tg_port2_vlan = 2 + + self.generate_config_file(frame_size, multistream, + runBidirec, tg_port1_vlan, + tg_port2_vlan, SearchRuntime, Package_Loss) + + self.server.execute("rm -f -- %s/opnfv-vsperf-cfg.lua" % + (self.moongen_dir)) + self.server._put_file_shell(self.VSPERF_CONFIG, + "%s/opnfv-vsperf-cfg.lua" + % (self.moongen_dir)) + + # execute moongen + cmd = ("cd %s;./MoonGen/build/MoonGen ./trafficgen.lua" + % (self.moongen_dir)) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + moongen_result = self.result_to_data(stdout) + LOG.info(moongen_result) + result.update(moongen_result) + + if "sla" in self.scenario_cfg: + throughput_rx_mpps = int( + self.scenario_cfg["sla"]["throughput_rx_mpps"]) + + self.verify_SLA( + throughput_rx_mpps <= moongen_result["tx_mpps"], + "sla_throughput_rx_mpps %f > throughput_rx_mpps(%f); " + % (throughput_rx_mpps, moongen_result["tx_mpps"])) + + def teardown(self): + """cleanup after the test execution""" + + # execute external setup script + self.setup_done = False diff --git a/yardstick/benchmark/scenarios/networking/netperf.py b/yardstick/benchmark/scenarios/networking/netperf.py index a8d9010ed..9f1a81413 100755 --- a/yardstick/benchmark/scenarios/networking/netperf.py +++ b/yardstick/benchmark/scenarios/networking/netperf.py @@ -104,7 +104,9 @@ class Netperf(base.Scenario): cmd_args = "-H %s -l %s -t %s" % (ipaddr, testlen, testname) # get test specific options - default_args = "-O 'THROUGHPUT,THROUGHPUT_UNITS,MEAN_LATENCY'" + output_opt = options.get( + "output_opt", "THROUGHPUT,THROUGHPUT_UNITS,MEAN_LATENCY") + default_args = "-O %s" % output_opt cmd_args += " -- %s" % default_args option_pair_list = [("send_msg_size", "-m"), ("recv_msg_size", "-M"), @@ -136,9 +138,9 @@ class Netperf(base.Scenario): sla_max_mean_latency = int( self.scenario_cfg["sla"]["mean_latency"]) - assert mean_latency <= sla_max_mean_latency, \ - "mean_latency %f > sla_max_mean_latency(%f); " % \ - (mean_latency, sla_max_mean_latency) + self.verify_SLA(mean_latency <= sla_max_mean_latency, + "mean_latency %f > sla_max_mean_latency(%f); " + % (mean_latency, sla_max_mean_latency)) def _test(): diff --git a/yardstick/benchmark/scenarios/networking/netperf_node.py b/yardstick/benchmark/scenarios/networking/netperf_node.py index d52e6b9e1..0ad2ecff5 100755 --- a/yardstick/benchmark/scenarios/networking/netperf_node.py +++ b/yardstick/benchmark/scenarios/networking/netperf_node.py @@ -156,9 +156,10 @@ class NetperfNode(base.Scenario): sla_max_mean_latency = int( self.scenario_cfg["sla"]["mean_latency"]) - assert mean_latency <= sla_max_mean_latency, \ - "mean_latency %f > sla_max_mean_latency(%f); " % \ - (mean_latency, sla_max_mean_latency) + self.verify_SLA( + mean_latency <= sla_max_mean_latency, + "mean_latency %f > sla_max_mean_latency(%f); " + % (mean_latency, sla_max_mean_latency)) def teardown(self): """remove netperf from nodes after test""" diff --git a/yardstick/benchmark/scenarios/networking/nstat.py b/yardstick/benchmark/scenarios/networking/nstat.py index 10c560769..ea067f8ab 100644 --- a/yardstick/benchmark/scenarios/networking/nstat.py +++ b/yardstick/benchmark/scenarios/networking/nstat.py @@ -121,4 +121,4 @@ class Nstat(base.Scenario): if rate > sla_rate: sla_error += "%s rate %f > sla:%s_rate(%f); " % \ (i, rate, i, sla_rate) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) diff --git a/yardstick/benchmark/scenarios/networking/ping.py b/yardstick/benchmark/scenarios/networking/ping.py index e7d9beea8..1c9510220 100644 --- a/yardstick/benchmark/scenarios/networking/ping.py +++ b/yardstick/benchmark/scenarios/networking/ping.py @@ -91,9 +91,10 @@ class Ping(base.Scenario): result.update(utils.flatten_dict_key(ping_result)) if sla_max_rtt is not None: sla_max_rtt = float(sla_max_rtt) - assert rtt_result[target_vm_name] <= sla_max_rtt,\ - "rtt %f > sla: max_rtt(%f); " % \ - (rtt_result[target_vm_name], sla_max_rtt) + self.verify_SLA( + rtt_result[target_vm_name] <= sla_max_rtt, + "rtt %f > sla: max_rtt(%f); " + % (rtt_result[target_vm_name], sla_max_rtt)) else: LOG.error("ping '%s' '%s' timeout", options, target_vm) # we need to specify a result to satisfy influxdb schema @@ -103,12 +104,13 @@ class Ping(base.Scenario): # store result before potential AssertionError result.update(utils.flatten_dict_key(ping_result)) if sla_max_rtt is not None: - raise AssertionError("packet dropped rtt {:f} > sla: max_rtt({:f})".format( - rtt_result[target_vm_name], sla_max_rtt)) - + self.verify_SLA(rtt_result[target_vm_name] <= sla_max_rtt, + "packet dropped rtt %f > sla: max_rtt(%f)" + % (rtt_result[target_vm_name], sla_max_rtt)) else: - raise AssertionError( - "packet dropped rtt {:f}".format(rtt_result[target_vm_name])) + self.verify_SLA(False, + "packet dropped rtt %f" + % (rtt_result[target_vm_name])) def _test(): # pragma: no cover diff --git a/yardstick/benchmark/scenarios/networking/ping6.py b/yardstick/benchmark/scenarios/networking/ping6.py index 74855a10f..377278004 100644 --- a/yardstick/benchmark/scenarios/networking/ping6.py +++ b/yardstick/benchmark/scenarios/networking/ping6.py @@ -59,8 +59,7 @@ class Ping6(base.Scenario): # pragma: no cover self._ssh_host(node_name) self.client._put_file_shell( self.pre_setup_script, '~/pre_setup.sh') - status, stdout, stderr = self.client.execute( - "sudo bash pre_setup.sh") + self.client.execute("sudo bash pre_setup.sh") def _get_controller_node(self, host_list): for host_name in host_list: @@ -122,7 +121,7 @@ class Ping6(base.Scenario): # pragma: no cover cmd = "sudo bash %s %s %s" % \ (setup_bash_file, self.openrc, self.external_network) LOG.debug("Executing setup command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) + self.client.execute(cmd) self.setup_done = True @@ -171,8 +170,9 @@ class Ping6(base.Scenario): # pragma: no cover result["rtt"] = float(stdout) if "sla" in self.scenario_cfg: sla_max_rtt = int(self.scenario_cfg["sla"]["max_rtt"]) - assert result["rtt"] <= sla_max_rtt, \ - "rtt %f > sla:max_rtt(%f); " % (result["rtt"], sla_max_rtt) + self.verify_SLA(result["rtt"] <= sla_max_rtt, + "rtt %f > sla:max_rtt(%f); " + % (result["rtt"], sla_max_rtt)) else: LOG.error("ping6 timeout!!!") self.run_done = True @@ -216,5 +216,4 @@ class Ping6(base.Scenario): # pragma: no cover self._ssh_host(node_name) self.client._put_file_shell( self.post_teardown_script, '~/post_teardown.sh') - status, stdout, stderr = self.client.execute( - "sudo bash post_teardown.sh") + self.client.execute("sudo bash post_teardown.sh") diff --git a/yardstick/benchmark/scenarios/networking/pktgen.py b/yardstick/benchmark/scenarios/networking/pktgen.py index b79b91539..c78108adb 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen.py +++ b/yardstick/benchmark/scenarios/networking/pktgen.py @@ -87,7 +87,7 @@ class Pktgen(base.Scenario): self.server.send_command(cmd) self.client.send_command(cmd) - """multiqueue setup""" + # multiqueue setup if not self._is_irqbalance_disabled(): self._disable_irqbalance() @@ -112,18 +112,14 @@ class Pktgen(base.Scenario): def _get_vnic_driver_name(self): cmd = "readlink /sys/class/net/%s/device/driver" % self.vnic_name LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) return os.path.basename(stdout.strip()) def _is_irqbalance_disabled(self): """Did we disable irqbalance already in the guest?""" is_disabled = False cmd = "grep ENABLED /etc/default/irqbalance" - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) if "0" in stdout: is_disabled = True @@ -132,49 +128,35 @@ class Pktgen(base.Scenario): def _disable_irqbalance(self): cmd = "sudo sed -i -e 's/ENABLED=\"1\"/ENABLED=\"0\"/g' " \ "/etc/default/irqbalance" - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) cmd = "sudo service irqbalance stop" - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) cmd = "sudo service irqbalance disable" - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) def _setup_irqmapping_ovs(self, queue_number): cmd = "grep 'virtio0-input.0' /proc/interrupts |" \ "awk '{match($0,/ +[0-9]+/)} " \ "{print substr($1,RSTART,RLENGTH-1)}'" - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) cmd = "grep 'virtio0-output.0' /proc/interrupts |" \ "awk '{match($0,/ +[0-9]+/)} " \ "{print substr($1,RSTART,RLENGTH-1)}'" - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) if queue_number == 1: return @@ -186,44 +168,32 @@ class Pktgen(base.Scenario): cmd = "grep 'virtio0-input.%s' /proc/interrupts |" \ "awk '{match($0,/ +[0-9]+/)} " \ "{print substr($1,RSTART,RLENGTH-1)}'" % (i) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ % (smp_affinity_mask, int(stdout)) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) cmd = "grep 'virtio0-output.%s' /proc/interrupts |" \ "awk '{match($0,/ +[0-9]+/)} " \ "{print substr($1,RSTART,RLENGTH-1)}'" % (i) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ % (smp_affinity_mask, int(stdout)) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) def _setup_irqmapping_sriov(self, queue_number): cmd = "grep '%s-TxRx-0' /proc/interrupts |" \ "awk '{match($0,/ +[0-9]+/)} " \ "{print substr($1,RSTART,RLENGTH-1)}'" % self.vnic_name - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) if queue_number == 1: return @@ -234,24 +204,18 @@ class Pktgen(base.Scenario): cmd = "grep '%s-TxRx-%s' /proc/interrupts |" \ "awk '{match($0,/ +[0-9]+/)} " \ "{print substr($1,RSTART,RLENGTH-1)}'" % (self.vnic_name, i) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ % (smp_affinity_mask, int(stdout)) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) def _get_sriov_queue_number(self): """Get queue number from server as both VMs are the same""" cmd = "grep %s-TxRx- /proc/interrupts | wc -l" % self.vnic_name LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) return int(stdout) def _get_available_queue_number(self): @@ -259,9 +223,7 @@ class Pktgen(base.Scenario): cmd = "sudo ethtool -l %s | grep Combined | head -1 |" \ "awk '{printf $2}'" % self.vnic_name LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) return int(stdout) def _get_usable_queue_number(self): @@ -269,9 +231,7 @@ class Pktgen(base.Scenario): cmd = "sudo ethtool -l %s | grep Combined | tail -1 |" \ "awk '{printf $2}'" % self.vnic_name LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) return int(stdout) def _enable_ovs_multiqueue(self): @@ -282,10 +242,8 @@ class Pktgen(base.Scenario): cmd = "sudo ethtool -L %s combined %s" % \ (self.vnic_name, available_queue_number) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.server.execute(cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.server.run(cmd) + self.client.run(cmd) return available_queue_number def _iptables_setup(self): @@ -294,9 +252,7 @@ class Pktgen(base.Scenario): "sudo iptables -A INPUT -p udp --dport 1000:%s -j DROP" \ % (1000 + self.number_of_ports) LOG.debug("Executing command: %s", cmd) - status, _, stderr = self.server.execute(cmd, timeout=SSH_TIMEOUT) - if status: - raise RuntimeError(stderr) + self.server.run(cmd, timeout=SSH_TIMEOUT) def _iptables_get_result(self): """Get packet statistics from server""" @@ -304,9 +260,7 @@ class Pktgen(base.Scenario): "awk '/dpts:1000:%s/ {{printf \"%%s\", $1}}'" \ % (1000 + self.number_of_ports) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.server.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.server.execute(cmd, raise_on_error=True) return int(stdout) def run(self, result): @@ -356,10 +310,8 @@ class Pktgen(base.Scenario): duration, queue_number, pps) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd, timeout=SSH_TIMEOUT) - - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.client.execute(cmd, raise_on_error=True, + timeout=SSH_TIMEOUT) result.update(jsonutils.loads(stdout)) @@ -374,8 +326,8 @@ class Pktgen(base.Scenario): if "sla" in self.scenario_cfg: LOG.debug("Lost packets %d - Lost ppm %d", (sent - received), ppm) sla_max_ppm = int(self.scenario_cfg["sla"]["max_ppm"]) - assert ppm <= sla_max_ppm, "ppm %d > sla_max_ppm %d; " \ - % (ppm, sla_max_ppm) + self.verify_SLA(ppm <= sla_max_ppm, + "ppm %d > sla_max_ppm %d; " % (ppm, sla_max_ppm)) def _test(): # pragma: no cover diff --git a/yardstick/benchmark/scenarios/networking/pktgen_dpdk.py b/yardstick/benchmark/scenarios/networking/pktgen_dpdk.py index ce8a7f497..efb7d8b5d 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen_dpdk.py +++ b/yardstick/benchmark/scenarios/networking/pktgen_dpdk.py @@ -70,39 +70,42 @@ class PktgenDPDKLatency(base.Scenario): def run(self, result): """execute the benchmark""" + options = self.scenario_cfg['options'] + eth1 = options.get("eth1", "ens4") + eth2 = options.get("eth2", "ens5") if not self.setup_done: self.setup() if not self.testpmd_args: - self.testpmd_args = utils.get_port_mac(self.client, 'eth2') + self.testpmd_args = utils.get_port_mac(self.client, eth2) if not self.pktgen_args: - server_rev_mac = utils.get_port_mac(self.server, 'eth1') - server_send_mac = utils.get_port_mac(self.server, 'eth2') - client_src_ip = utils.get_port_ip(self.client, 'eth1') - client_dst_ip = utils.get_port_ip(self.client, 'eth2') + server_rev_mac = utils.get_port_mac(self.server, eth1) + server_send_mac = utils.get_port_mac(self.server, eth2) + client_src_ip = utils.get_port_ip(self.client, eth1) + client_dst_ip = utils.get_port_ip(self.client, eth2) self.pktgen_args = [client_src_ip, client_dst_ip, server_rev_mac, server_send_mac] - options = self.scenario_cfg['options'] packetsize = options.get("packetsize", 64) rate = options.get("rate", 100) - cmd = "screen sudo -E bash ~/testpmd_fwd.sh %s " % (self.testpmd_args) + cmd = "screen sudo -E bash ~/testpmd_fwd.sh %s %s %s" % \ + (self.testpmd_args, eth1, eth2) LOG.debug("Executing command: %s", cmd) self.server.send_command(cmd) time.sleep(1) - cmd = "screen sudo -E bash ~/pktgen_dpdk.sh %s %s %s %s %s %s" % \ + cmd = "screen sudo -E bash ~/pktgen_dpdk.sh %s %s %s %s %s %s %s %s" % \ (self.pktgen_args[0], self.pktgen_args[1], self.pktgen_args[2], - self.pktgen_args[3], rate, packetsize) + self.pktgen_args[3], rate, packetsize, eth1, eth2) LOG.debug("Executing command: %s", cmd) self.client.send_command(cmd) # wait for finishing test - time.sleep(1) + time.sleep(60) cmd = r"""\ cat ~/result.log -vT \ @@ -110,10 +113,7 @@ cat ~/result.log -vT \ {print substr($0,RSTART,RLENGTH)}' \ |grep -v ^$ |awk '{if ($2 != 0) print $2}'\ """ - client_status, client_stdout, client_stderr = self.client.execute(cmd) - - if client_status: - raise RuntimeError(client_stderr) + _, client_stdout, _ = self.client.execute(cmd, raise_on_error=True) avg_latency = 0 if client_stdout: @@ -132,4 +132,4 @@ cat ~/result.log -vT \ LOG.info("sla_max_latency: %d", sla_max_latency) debug_info = "avg_latency %d > sla_max_latency %d" \ % (avg_latency, sla_max_latency) - assert avg_latency <= sla_max_latency, debug_info + self.verify_SLA(avg_latency <= sla_max_latency, debug_info) diff --git a/yardstick/benchmark/scenarios/networking/pktgen_dpdk_latency_benchmark.bash b/yardstick/benchmark/scenarios/networking/pktgen_dpdk_latency_benchmark.bash index b872aa3df..dcd5a9bfb 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen_dpdk_latency_benchmark.bash +++ b/yardstick/benchmark/scenarios/networking/pktgen_dpdk_latency_benchmark.bash @@ -7,7 +7,7 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -!/bin/sh +#!/bin/sh set -e @@ -18,6 +18,11 @@ FWD_REV_MAC=$3 # MAC address of forwarding receiver in VM B FWD_SEND_MAC=$4 # MAC address of forwarding sender in VM B RATE=$5 # packet rate in percentage PKT_SIZE=$6 # packet size +ETH1=$7 +ETH2=$8 + +DPDK_VERSION="dpdk-17.02" +PKTGEN_VERSION="pktgen-3.2.12" load_modules() @@ -31,13 +36,13 @@ load_modules() if lsmod | grep "igb_uio" &> /dev/null ; then echo "igb_uio module is loaded" else - insmod /dpdk/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko + insmod /opt/tempT/$DPDK_VERSION/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko fi if lsmod | grep "rte_kni" &> /dev/null ; then echo "rte_kni module is loaded" else - insmod /dpdk/x86_64-native-linuxapp-gcc/kmod/rte_kni.ko + insmod /opt/tempT/$DPDK_VERSION/x86_64-native-linuxapp-gcc/kmod/rte_kni.ko fi } @@ -48,8 +53,10 @@ change_permissions() } add_interface_to_dpdk(){ + ip link set $ETH1 down + ip link set $ETH2 down interfaces=$(lspci |grep Eth |tail -n +2 |awk '{print $1}') - /dpdk/tools/dpdk-devbind.py --bind=igb_uio $interfaces + /opt/tempT/$DPDK_VERSION/usertools/dpdk-devbind.py --bind=igb_uio $interfaces } @@ -106,20 +113,14 @@ spawn ./app/app/x86_64-native-linuxapp-gcc/pktgen -c 0x07 -n 4 -b $blacklist -- expect "Pktgen>" send "\n" expect "Pktgen>" -send "screen on\n" +send "on\n" expect "Pktgen>" set count 10 while { $count } { send "page latency\n" - expect { - timeout { send "\n" } - -regexp {..*} { - set result "${result}$expect_out(0,string)" - set timeout 1 - exp_continue - } - "Pktgen>" - } + expect -re "(..*)" + set result "${result}$expect_out(0,string)" + set timeout 1 set count [expr $count-1] } send "stop 0\n" @@ -136,7 +137,7 @@ EOF run_pktgen() { blacklist=$(lspci |grep Eth |awk '{print $1}'|head -1) - cd /pktgen-dpdk + cd /opt/tempT/$PKTGEN_VERSION touch /home/ubuntu/result.log result_log="/home/ubuntu/result.log" sudo expect /home/ubuntu/pktgen.exp $blacklist $result_log @@ -153,4 +154,3 @@ main() } main - diff --git a/yardstick/benchmark/scenarios/networking/pktgen_dpdk_throughput.py b/yardstick/benchmark/scenarios/networking/pktgen_dpdk_throughput.py index 497e59ee8..97b9cf73f 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen_dpdk_throughput.py +++ b/yardstick/benchmark/scenarios/networking/pktgen_dpdk_throughput.py @@ -143,11 +143,11 @@ class PktgenDPDK(base.Scenario): cmd = "ip a | grep eth1 2>/dev/null" LOG.debug("Executing command: %s in %s", cmd, host) if "server" in host: - status, stdout, stderr = self.server.execute(cmd) + _, stdout, _ = self.server.execute(cmd) if stdout: is_run = False else: - status, stdout, stderr = self.client.execute(cmd) + _, stdout, _ = self.client.execute(cmd) if stdout: is_run = False @@ -222,5 +222,5 @@ class PktgenDPDK(base.Scenario): ppm += (sent - received) % sent > 0 LOG.debug("Lost packets %d - Lost ppm %d", (sent - received), ppm) sla_max_ppm = int(self.scenario_cfg["sla"]["max_ppm"]) - assert ppm <= sla_max_ppm, "ppm %d > sla_max_ppm %d; " \ - % (ppm, sla_max_ppm) + self.verify_SLA(ppm <= sla_max_ppm, "ppm %d > sla_max_ppm %d; " + % (ppm, sla_max_ppm)) diff --git a/yardstick/benchmark/scenarios/networking/sfc_openstack.py b/yardstick/benchmark/scenarios/networking/sfc_openstack.py index d5feabbbe..aaab2131a 100644 --- a/yardstick/benchmark/scenarios/networking/sfc_openstack.py +++ b/yardstick/benchmark/scenarios/networking/sfc_openstack.py @@ -34,11 +34,13 @@ def get_credentials(service): # pragma: no cover # The most common way to pass these info to the script is to do it through # environment variables. + # NOTE(ralonsoh): OS_TENANT_NAME is deprecated. + project_name = os.environ.get('OS_PROJECT_NAME', 'admin') creds.update({ "username": os.environ.get('OS_USERNAME', "admin"), password: os.environ.get("OS_PASSWORD", 'admin'), "auth_url": os.environ.get("OS_AUTH_URL"), - tenant: os.environ.get("OS_TENANT_NAME", "admin"), + tenant: os.environ.get("OS_TENANT_NAME", project_name), }) cacert = os.environ.get("OS_CACERT") if cacert is not None: @@ -59,7 +61,7 @@ def get_instances(nova_client): # pragma: no cover try: instances = nova_client.servers.list(search_opts={'all_tenants': 1}) return instances - except Exception as e: + except Exception as e: # pylint: disable=broad-except print("Error [get_instances(nova_client)]:", e) return None @@ -72,7 +74,7 @@ def get_SFs(nova_client): # pragma: no cover if "sfc_test" not in instance.name: SFs.append(instance) return SFs - except Exception as e: + except Exception as e: # pylint: disable=broad-except print("Error [get_SFs(nova_client)]:", e) return None @@ -93,7 +95,7 @@ def create_floating_ips(neutron_client): # pragma: no cover ip_json = neutron_client.create_floatingip({'floatingip': props}) fip_addr = ip_json['floatingip']['floating_ip_address'] ips.append(fip_addr) - except Exception as e: + except Exception as e: # pylint: disable=broad-except print("Error [create_floating_ip(neutron_client)]:", e) return None return ips @@ -106,7 +108,7 @@ def floatIPtoSFs(SFs, floatips): # pragma: no cover SF.add_floating_ip(floatips[i]) i = i + 1 return True - except Exception as e: + except Exception as e: # pylint: disable=broad-except print(("Error [add_floating_ip(nova_client, '%s', '%s')]:" % (SF, floatips[i]), e)) return False @@ -122,7 +124,3 @@ def get_an_IP(): # pragma: no cover floatips = create_floating_ips(neutron_client) floatIPtoSFs(SFs, floatips) return floatips - - -if __name__ == '__main__': # pragma: no cover - get_an_IP() diff --git a/yardstick/benchmark/scenarios/networking/testpmd_fwd.bash b/yardstick/benchmark/scenarios/networking/testpmd_fwd.bash index 247a8a833..30b63a734 100644 --- a/yardstick/benchmark/scenarios/networking/testpmd_fwd.bash +++ b/yardstick/benchmark/scenarios/networking/testpmd_fwd.bash @@ -13,6 +13,10 @@ set -e # Commandline arguments DST_MAC=$1 # MAC address of the peer port +ETH1=$2 +ETH2=$3 + +DPDK_VERSION="dpdk-17.02" load_modules() { @@ -25,13 +29,13 @@ load_modules() if lsmod | grep "igb_uio" &> /dev/null ; then echo "igb_uio module is loaded" else - insmod /dpdk/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko + insmod /opt/tempT/$DPDK_VERSION/x86_64-native-linuxapp-gcc/kmod/igb_uio.ko fi if lsmod | grep "rte_kni" &> /dev/null ; then echo "rte_kni module is loaded" else - insmod /dpdk/x86_64-native-linuxapp-gcc/kmod/rte_kni.ko + insmod /opt/tempT/$DPDK_VERSION/x86_64-native-linuxapp-gcc/kmod/rte_kni.ko fi } @@ -42,15 +46,17 @@ change_permissions() } add_interface_to_dpdk(){ + ip link set $ETH1 down + ip link set $ETH2 down interfaces=$(lspci |grep Eth |tail -n +2 |awk '{print $1}') - /dpdk/tools/dpdk-devbind.py --bind=igb_uio $interfaces + /opt/tempT/$DPDK_VERSION/usertools//dpdk-devbind.py --bind=igb_uio $interfaces } run_testpmd() { blacklist=$(lspci |grep Eth |awk '{print $1}'|head -1) - cd /dpdk - sudo ./destdir/bin/testpmd -c 0x07 -n 4 -b $blacklist -- -a --eth-peer=1,$DST_MAC --forward-mode=mac + cd /opt/tempT/$DPDK_VERSION/x86_64-native-linuxapp-gcc/app + sudo ./testpmd -c 0x07 -n 4 -b $blacklist -- -a --eth-peer=1,$DST_MAC --forward-mode=mac } main() diff --git a/yardstick/benchmark/scenarios/networking/vnf_generic.py b/yardstick/benchmark/scenarios/networking/vnf_generic.py index b94bfc9ab..c5e75d093 100644 --- a/yardstick/benchmark/scenarios/networking/vnf_generic.py +++ b/yardstick/benchmark/scenarios/networking/vnf_generic.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016-2017 Intel Corporation +# Copyright (c) 2016-2019 Intel Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,168 +11,116 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" NSPerf specific scenario definition """ - -from __future__ import absolute_import - -import logging -import errno - -import ipaddress import copy +import ipaddress +from itertools import chain +import logging import os import sys -import re -from itertools import chain +import time import six import yaml -from collections import defaultdict -from yardstick.benchmark.scenarios import base +from yardstick.benchmark.contexts import base as context_base +from yardstick.benchmark.scenarios import base as scenario_base from yardstick.common.constants import LOG_DIR +from yardstick.common import exceptions from yardstick.common.process import terminate_children -from yardstick.common.utils import import_modules_from_package, itersubclasses -from yardstick.common.yaml_loader import yaml_load +from yardstick.common import utils from yardstick.network_services.collector.subscriber import Collector from yardstick.network_services.vnf_generic import vnfdgen from yardstick.network_services.vnf_generic.vnf.base import GenericVNF -from yardstick.network_services.traffic_profile.base import TrafficProfile +from yardstick.network_services import traffic_profile +from yardstick.network_services.traffic_profile import base as tprofile_base from yardstick.network_services.utils import get_nsb_option from yardstick import ssh -LOG = logging.getLogger(__name__) - - -class SSHError(Exception): - """Class handles ssh connection error exception""" - pass - - -class SSHTimeout(SSHError): - """Class handles ssh connection timeout exception""" - pass +traffic_profile.register_modules() -class IncorrectConfig(Exception): - """Class handles incorrect configuration during setup""" - pass - - -class IncorrectSetup(Exception): - """Class handles incorrect setup during setup""" - pass - - -class SshManager(object): - def __init__(self, node, timeout=120): - super(SshManager, self).__init__() - self.node = node - self.conn = None - self.timeout = timeout - - def __enter__(self): - """ - args -> network device mappings - returns -> ssh connection ready to be used - """ - try: - self.conn = ssh.SSH.from_node(self.node) - self.conn.wait(timeout=self.timeout) - except SSHError as error: - LOG.info("connect failed to %s, due to %s", self.node["ip"], error) - # self.conn defaults to None - return self.conn - - def __exit__(self, exc_type, exc_val, exc_tb): - if self.conn: - self.conn.close() - - -def find_relative_file(path, task_path): - """ - Find file in one of places: in abs of path or - relative to TC scenario file. In this order. - - :param path: - :param task_path: - :return str: full path to file - """ - # fixme: create schema to validate all fields have been provided - for lookup in [os.path.abspath(path), os.path.join(task_path, path)]: - try: - with open(lookup): - return lookup - except IOError: - pass - raise IOError(errno.ENOENT, 'Unable to find {} file'.format(path)) - - -def open_relative_file(path, task_path): - try: - return open(path) - except IOError as e: - if e.errno == errno.ENOENT: - return open(os.path.join(task_path, path)) - raise +LOG = logging.getLogger(__name__) -class NetworkServiceTestCase(base.Scenario): - """Class handles Generic framework to do pre-deployment VNF & - Network service testing """ +class NetworkServiceBase(scenario_base.Scenario): + """Base class for Network service testing scenarios""" - __scenario_type__ = "NSPerf" + __scenario_type__ = "" - def __init__(self, scenario_cfg, context_cfg): # Yardstick API - super(NetworkServiceTestCase, self).__init__() + def __init__(self, scenario_cfg, context_cfg): # pragma: no cover + super(NetworkServiceBase, self).__init__() self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg - # fixme: create schema to validate all fields have been provided - with open_relative_file(scenario_cfg["topology"], - scenario_cfg['task_path']) as stream: - topology_yaml = yaml_load(stream) - - self.topology = topology_yaml["nsd:nsd-catalog"]["nsd"][0] + self._render_topology() self.vnfs = [] self.collector = None self.traffic_profile = None self.node_netdevs = {} + self.bin_path = get_nsb_option('bin_path', '') + + def run(self, *args): + pass + + def teardown(self): + """ Stop the collector and terminate VNF & TG instance + + :return + """ + + try: + try: + self.collector.stop() + for vnf in self.vnfs: + LOG.info("Stopping %s", vnf.name) + vnf.terminate() + LOG.debug("all VNFs terminated: %s", ", ".join(vnf.name for vnf in self.vnfs)) + finally: + terminate_children() + except Exception: + # catch any exception in teardown and convert to simple exception + # never pass exceptions back to multiprocessing, because some exceptions can + # be unpicklable + # https://bugs.python.org/issue9400 + LOG.exception("") + raise RuntimeError("Error in teardown") + + def is_ended(self): + return self.traffic_profile is not None and self.traffic_profile.is_ended() def _get_ip_flow_range(self, ip_start_range): + """Retrieve a CIDR first and last viable IPs - # IP range is specified as 'x.x.x.x-y.y.y.y' + :param ip_start_range: could be the IP range itself or a dictionary + with the host name and the port. + :return: (str) IP range (min, max) with this format "x.x.x.x-y.y.y.y" + """ if isinstance(ip_start_range, six.string_types): return ip_start_range - node_name, range_or_interface = next(iter(ip_start_range.items()), (None, '0.0.0.0')) + node_name, range_or_interface = next(iter(ip_start_range.items()), + (None, '0.0.0.0')) if node_name is None: - # we are manually specifying the range - ip_addr_range = range_or_interface + return range_or_interface + + node = self.context_cfg['nodes'].get(node_name, {}) + interface = node.get('interfaces', {}).get(range_or_interface) + if interface: + ip = interface['local_ip'] + mask = interface['netmask'] else: - node = self.context_cfg["nodes"].get(node_name, {}) - try: - # the ip_range is the interface name - interface = node.get("interfaces", {})[range_or_interface] - except KeyError: - ip = "0.0.0.0" - mask = "255.255.255.0" - else: - ip = interface["local_ip"] - # we can't default these values, they must both exist to be valid - mask = interface["netmask"] - - ipaddr = ipaddress.ip_network(six.text_type('{}/{}'.format(ip, mask)), strict=False) - hosts = list(ipaddr.hosts()) - if len(hosts) > 2: - # skip the first host in case of gateway - ip_addr_range = "{}-{}".format(hosts[1], hosts[-1]) - else: - LOG.warning("Only single IP in range %s", ipaddr) - # fall back to single IP range - ip_addr_range = ip + ip = '0.0.0.0' + mask = '255.255.255.0' + + ipaddr = ipaddress.ip_network( + six.text_type('{}/{}'.format(ip, mask)), strict=False) + if ipaddr.prefixlen + 2 < ipaddr.max_prefixlen: + ip_addr_range = '{}-{}'.format(ipaddr[2], ipaddr[-2]) + else: + LOG.warning('Only single IP in range %s', ipaddr) + ip_addr_range = ip return ip_addr_range def _get_traffic_flow(self): @@ -196,7 +144,15 @@ class NetworkServiceTestCase(base.Scenario): for index, dst_port in enumerate(fflow.get("dst_port", [])): flow["dst_port_{}".format(index)] = dst_port - flow["count"] = fflow["count"] + if "count" in fflow: + flow["count"] = fflow["count"] + + if "srcseed" in fflow: + flow["srcseed"] = fflow["srcseed"] + + if "dstseed" in fflow: + flow["dstseed"] = fflow["dstseed"] + except KeyError: flow = {} return {"flow": flow} @@ -208,43 +164,95 @@ class NetworkServiceTestCase(base.Scenario): imix = {} return imix + def _get_ip_priority(self): + try: + priority = self.scenario_cfg['options']['priority'] + except KeyError: + priority = {} + return priority + def _get_traffic_profile(self): profile = self.scenario_cfg["traffic_profile"] path = self.scenario_cfg["task_path"] - with open_relative_file(profile, path) as infile: + with utils.open_relative_file(profile, path) as infile: return infile.read() + def _get_duration(self): + options = self.scenario_cfg.get('options', {}) + return options.get('duration', + tprofile_base.TrafficProfileConfig.DEFAULT_DURATION) + + def _key_list_to_dict(self, key, value_list): + value_dict = {} + try: + for index, count in enumerate(value_list[key]): + value_dict["{}_{}".format(key, index)] = count + except KeyError: + value_dict = {} + + return value_dict + + def _get_simulated_users(self): + users = self.scenario_cfg.get("options", {}).get("simulated_users", {}) + simulated_users = self._key_list_to_dict("uplink", users) + return {"simulated_users": simulated_users} + + def _get_page_object(self): + objects = self.scenario_cfg.get("options", {}).get("page_object", {}) + page_object = self._key_list_to_dict("uplink", objects) + return {"page_object": page_object} + def _fill_traffic_profile(self): - traffic_mapping = self._get_traffic_profile() - traffic_map_data = { + tprofile = self._get_traffic_profile() + extra_args = self.scenario_cfg.get('extra_args', {}) + tprofile_data = { 'flow': self._get_traffic_flow(), 'imix': self._get_traffic_imix(), - TrafficProfile.UPLINK: {}, - TrafficProfile.DOWNLINK: {}, - } + 'priority': self._get_ip_priority(), + tprofile_base.TrafficProfile.UPLINK: {}, + tprofile_base.TrafficProfile.DOWNLINK: {}, + 'extra_args': extra_args, + 'duration': self._get_duration(), + 'page_object': self._get_page_object(), + 'simulated_users': self._get_simulated_users()} + traffic_vnfd = vnfdgen.generate_vnfd(tprofile, tprofile_data) + + traffic_config = \ + self.scenario_cfg.get("options", {}).get("traffic_config", {}) + + traffic_vnfd.setdefault("traffic_profile", {}) + traffic_vnfd["traffic_profile"].update(traffic_config) + + self.traffic_profile = \ + tprofile_base.TrafficProfile.get(traffic_vnfd) + + def _get_topology(self): + topology = self.scenario_cfg["topology"] + path = self.scenario_cfg["task_path"] + with utils.open_relative_file(topology, path) as infile: + return infile.read() - traffic_vnfd = vnfdgen.generate_vnfd(traffic_mapping, traffic_map_data) - self.traffic_profile = TrafficProfile.get(traffic_vnfd) - return self.traffic_profile + def _render_topology(self): + topology = self._get_topology() + topology_args = self.scenario_cfg.get('extra_args', {}) + topolgy_data = { + 'extra_args': topology_args + } + topology_yaml = vnfdgen.generate_vnfd(topology, topolgy_data) + self.topology = topology_yaml["nsd:nsd-catalog"]["nsd"][0] - def _find_vnf_name_from_id(self, vnf_id): + def _find_vnf_name_from_id(self, vnf_id): # pragma: no cover return next((vnfd["vnfd-id-ref"] for vnfd in self.topology["constituent-vnfd"] if vnf_id == vnfd["member-vnf-index"]), None) - @staticmethod - def get_vld_networks(networks): - # network name is vld_id - vld_map = {} - for name, n in networks.items(): - try: - vld_map[n['vld_id']] = n - except KeyError: - vld_map[name] = n - return vld_map + def _find_vnfd_from_vnf_idx(self, vnf_id): # pragma: no cover + return next((vnfd + for vnfd in self.topology["constituent-vnfd"] + if vnf_id == vnfd["member-vnf-index"]), None) @staticmethod - def find_node_if(nodes, name, if_name, vld_id): + def find_node_if(nodes, name, if_name, vld_id): # pragma: no cover try: # check for xe0, xe1 intf = nodes[name]["interfaces"][if_name] @@ -260,8 +268,9 @@ class NetworkServiceTestCase(base.Scenario): try: node0_data, node1_data = vld["vnfd-connection-point-ref"] except (ValueError, TypeError): - raise IncorrectConfig("Topology file corrupted, " - "wrong endpoint count for connection") + raise exceptions.IncorrectConfig( + error_msg='Topology file corrupted, wrong endpoint count ' + 'for connection') node0_name = self._find_vnf_name_from_id(node0_data["member-vnf-index-ref"]) node1_name = self._find_vnf_name_from_id(node1_data["member-vnf-index-ref"]) @@ -293,7 +302,9 @@ class NetworkServiceTestCase(base.Scenario): node1_if["peer_ifname"] = node0_if_name # just load the network - vld_networks = self.get_vld_networks(self.context_cfg["networks"]) + vld_networks = {n.get('vld_id', name): n for name, n in + self.context_cfg["networks"].items()} + node0_if["network"] = vld_networks.get(vld["id"], {}) node1_if["network"] = vld_networks.get(vld["id"], {}) @@ -305,15 +316,17 @@ class NetworkServiceTestCase(base.Scenario): except KeyError: LOG.exception("") - raise IncorrectConfig("Required interface not found, " - "topology file corrupted") + raise exceptions.IncorrectConfig( + error_msg='Required interface not found, topology file ' + 'corrupted') for vld in self.topology['vld']: try: node0_data, node1_data = vld["vnfd-connection-point-ref"] except (ValueError, TypeError): - raise IncorrectConfig("Topology file corrupted, " - "wrong endpoint count for connection") + raise exceptions.IncorrectConfig( + error_msg='Topology file corrupted, wrong endpoint count ' + 'for connection') node0_name = self._find_vnf_name_from_id(node0_data["member-vnf-index-ref"]) node1_name = self._find_vnf_name_from_id(node1_data["member-vnf-index-ref"]) @@ -332,55 +345,14 @@ class NetworkServiceTestCase(base.Scenario): node0_if["peer_intf"] = node1_copy node1_if["peer_intf"] = node0_copy - def _find_vnfd_from_vnf_idx(self, vnf_idx): - return next((vnfd for vnfd in self.topology["constituent-vnfd"] - if vnf_idx == vnfd["member-vnf-index"]), None) - - def _update_context_with_topology(self): + def _update_context_with_topology(self): # pragma: no cover for vnfd in self.topology["constituent-vnfd"]: vnf_idx = vnfd["member-vnf-index"] vnf_name = self._find_vnf_name_from_id(vnf_idx) vnfd = self._find_vnfd_from_vnf_idx(vnf_idx) self.context_cfg["nodes"][vnf_name].update(vnfd) - def _probe_netdevs(self, node, node_dict, timeout=120): - try: - return self.node_netdevs[node] - except KeyError: - pass - - netdevs = {} - cmd = "PATH=$PATH:/sbin:/usr/sbin ip addr show" - - with SshManager(node_dict, timeout=timeout) as conn: - if conn: - exit_status = conn.execute(cmd)[0] - if exit_status != 0: - raise IncorrectSetup("Node's %s lacks ip tool." % node) - exit_status, stdout, _ = conn.execute( - self.FIND_NETDEVICE_STRING) - if exit_status != 0: - raise IncorrectSetup( - "Cannot find netdev info in sysfs" % node) - netdevs = node_dict['netdevs'] = self.parse_netdev_info(stdout) - - self.node_netdevs[node] = netdevs - return netdevs - - @classmethod - def _probe_missing_values(cls, netdevs, network): - - mac_lower = network['local_mac'].lower() - for netdev in netdevs.values(): - if netdev['address'].lower() != mac_lower: - continue - network.update({ - 'driver': netdev['driver'], - 'vpci': netdev['pci_bus_id'], - 'ifindex': netdev['ifindex'], - }) - - def _generate_pod_yaml(self): + def _generate_pod_yaml(self): # pragma: no cover context_yaml = os.path.join(LOG_DIR, "pod-{}.yaml".format(self.scenario_cfg['task_id'])) # convert OrderedDict to a list # pod.yaml nodes is a list @@ -394,7 +366,7 @@ class NetworkServiceTestCase(base.Scenario): explicit_start=True) @staticmethod - def _serialize_node(node): + def _serialize_node(node): # pragma: no cover new_node = copy.deepcopy(node) # name field is required # remove context suffix @@ -405,96 +377,31 @@ class NetworkServiceTestCase(base.Scenario): pass return new_node - TOPOLOGY_REQUIRED_KEYS = frozenset({ - "vpci", "local_ip", "netmask", "local_mac", "driver"}) - def map_topology_to_infrastructure(self): """ This method should verify if the available resources defined in pod.yaml match the topology.yaml file. :return: None. Side effect: context_cfg is updated """ - num_nodes = len(self.context_cfg["nodes"]) - # OpenStack instance creation time is probably proportional to the number - # of instances - timeout = 120 * num_nodes - for node, node_dict in self.context_cfg["nodes"].items(): - - for network in node_dict["interfaces"].values(): - missing = self.TOPOLOGY_REQUIRED_KEYS.difference(network) - if not missing: - continue - - # only ssh probe if there are missing values - # ssh probe won't work on Ixia, so we had better define all our values - try: - netdevs = self._probe_netdevs(node, node_dict, timeout=timeout) - except (SSHError, SSHTimeout): - raise IncorrectConfig( - "Unable to probe missing interface fields '%s', on node %s " - "SSH Error" % (', '.join(missing), node)) - try: - self._probe_missing_values(netdevs, network) - except KeyError: - pass - else: - missing = self.TOPOLOGY_REQUIRED_KEYS.difference( - network) - if missing: - raise IncorrectConfig( - "Require interface fields '%s' not found, topology file " - "corrupted" % ', '.join(missing)) - - # we have to generate pod.yaml here so we have vpci and driver - self._generate_pod_yaml() # 3. Use topology file to find connections & resolve dest address self._resolve_topology() self._update_context_with_topology() - FIND_NETDEVICE_STRING = r"""find /sys/devices/pci* -type d -name net -exec sh -c '{ grep -sH ^ \ -$1/ifindex $1/address $1/operstate $1/device/vendor $1/device/device \ -$1/device/subsystem_vendor $1/device/subsystem_device ; \ -printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ -' sh \{\}/* \; -""" - BASE_ADAPTER_RE = re.compile( - '^/sys/devices/(.*)/net/([^/]*)/([^:]*):(.*)$', re.M) - - @classmethod - def parse_netdev_info(cls, stdout): - network_devices = defaultdict(dict) - matches = cls.BASE_ADAPTER_RE.findall(stdout) - for bus_path, interface_name, name, value in matches: - dirname, bus_id = os.path.split(bus_path) - if 'virtio' in bus_id: - # for some stupid reason VMs include virtio1/ - # in PCI device path - bus_id = os.path.basename(dirname) - # remove extra 'device/' from 'device/vendor, - # device/subsystem_vendor', etc. - if 'device/' in name: - name = name.split('/')[1] - network_devices[interface_name][name] = value - network_devices[interface_name][ - 'interface_name'] = interface_name - network_devices[interface_name]['pci_bus_id'] = bus_id - # convert back to regular dict - return dict(network_devices) - @classmethod - def get_vnf_impl(cls, vnf_model_id): + def get_vnf_impl(cls, vnf_model_id): # pragma: no cover """ Find the implementing class from vnf_model["vnf"]["name"] field :param vnf_model_id: parsed vnfd model ID field :return: subclass of GenericVNF """ - import_modules_from_package( + utils.import_modules_from_package( "yardstick.network_services.vnf_generic.vnf") expected_name = vnf_model_id classes_found = [] def impl(): - for name, class_ in ((c.__name__, c) for c in itersubclasses(GenericVNF)): + for name, class_ in ((c.__name__, c) for c in + utils.itersubclasses(GenericVNF)): if name == expected_name: yield class_ classes_found.append(name) @@ -504,11 +411,12 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ except StopIteration: pass - raise IncorrectConfig("No implementation for %s found in %s" % - (expected_name, classes_found)) + message = ('No implementation for %s found in %s' + % (expected_name, classes_found)) + raise exceptions.IncorrectConfig(error_msg=message) @staticmethod - def create_interfaces_from_node(vnfd, node): + def create_interfaces_from_node(vnfd, node): # pragma: no cover ext_intfs = vnfd["vdu"][0]["external-interface"] = [] # have to sort so xe0 goes first for intf_name, intf in sorted(node['interfaces'].items()): @@ -547,7 +455,7 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ context_cfg = self.context_cfg vnfs = [] - # we assume OrderedDict for consistenct in instantiation + # we assume OrderedDict for consistency in instantiation for node_name, node in context_cfg["nodes"].items(): LOG.debug(node) try: @@ -556,7 +464,7 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ LOG.debug("no model for %s, skipping", node_name) continue file_path = scenario_cfg['task_path'] - with open_relative_file(file_name, file_path) as stream: + with utils.open_relative_file(file_name, file_path) as stream: vnf_model = stream.read() vnfd = vnfdgen.generate_vnfd(vnf_model, node) # TODO: here add extra context_cfg["nodes"] regardless of template @@ -576,11 +484,26 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ self.vnfs = vnfs return vnfs - def setup(self): - """ Setup infrastructure, provission VNFs & start traffic + def pre_run_wait_time(self, time_seconds): # pragma: no cover + """Time waited before executing the run method""" + time.sleep(time_seconds) - :return: - """ + def post_run_wait_time(self, time_seconds): # pragma: no cover + """Time waited after executing the run method""" + pass + + +class NetworkServiceTestCase(NetworkServiceBase): + """Class handles Generic framework to do pre-deployment VNF & + Network service testing """ + + __scenario_type__ = "NSPerf" + + def __init__(self, scenario_cfg, context_cfg): # pragma: no cover + super(NetworkServiceTestCase, self).__init__(scenario_cfg, context_cfg) + + def setup(self): + """Setup infrastructure, provission VNFs & start traffic""" # 1. Verify if infrastructure mapping can meet topology self.map_topology_to_infrastructure() # 1a. Load VNF models @@ -606,13 +529,16 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ vnf.terminate() raise + # we have to generate pod.yaml here after VNF has probed so we know vpci and driver + self._generate_pod_yaml() + # 3. Run experiment # Start listeners first to avoid losing packets for traffic_gen in traffic_runners: traffic_gen.listen_traffic(self.traffic_profile) # register collector with yardstick for KPI collection. - self.collector = Collector(self.vnfs, self.context_cfg["nodes"], self.traffic_profile) + self.collector = Collector(self.vnfs, context_base.Context.get_physical_nodes()) self.collector.start() # Start the actual traffic @@ -634,25 +560,125 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ result.update(self.collector.get_kpi()) - def teardown(self): - """ Stop the collector and terminate VNF & TG instance - :return +class NetworkServiceRFC2544(NetworkServiceBase): + """Class handles RFC2544 Network service testing""" + + __scenario_type__ = "NSPerf-RFC2544" + + def __init__(self, scenario_cfg, context_cfg): # pragma: no cover + super(NetworkServiceRFC2544, self).__init__(scenario_cfg, context_cfg) + + def setup(self): + """Setup infrastructure, provision VNFs""" + self.map_topology_to_infrastructure() + self.load_vnf_models() + + traffic_runners = [vnf for vnf in self.vnfs if vnf.runs_traffic] + non_traffic_runners = [vnf for vnf in self.vnfs if not vnf.runs_traffic] + try: + for vnf in chain(traffic_runners, non_traffic_runners): + LOG.info("Instantiating %s", vnf.name) + vnf.instantiate(self.scenario_cfg, self.context_cfg) + LOG.info("Waiting for %s to instantiate", vnf.name) + vnf.wait_for_instantiate() + except: + LOG.exception("") + for vnf in self.vnfs: + vnf.terminate() + raise + + self._generate_pod_yaml() + + def run(self, output): + """ Run experiment + + :param output: scenario output to push results + :return: None """ + self._fill_traffic_profile() + + traffic_runners = [vnf for vnf in self.vnfs if vnf.runs_traffic] + + for traffic_gen in traffic_runners: + traffic_gen.listen_traffic(self.traffic_profile) + + self.collector = Collector(self.vnfs, + context_base.Context.get_physical_nodes()) + self.collector.start() + + test_completed = False + while not test_completed: + for traffic_gen in traffic_runners: + LOG.info("Run traffic on %s", traffic_gen.name) + traffic_gen.run_traffic_once(self.traffic_profile) + + test_completed = True + for traffic_gen in traffic_runners: + # wait for all tg to complete running traffic + status = traffic_gen.wait_on_traffic() + LOG.info("Run traffic on %s complete status=%s", + traffic_gen.name, status) + if status == 'CONTINUE': + # continue running if at least one tg is running + test_completed = False + + output.push(self.collector.get_kpi()) + + self.collector.stop() + +class NetworkServiceRFC3511(NetworkServiceBase): + """Class handles RFC3511 Network service testing""" + + __scenario_type__ = "NSPerf-RFC3511" + + def __init__(self, scenario_cfg, context_cfg): # pragma: no cover + super(NetworkServiceRFC3511, self).__init__(scenario_cfg, context_cfg) + + def setup(self): + """Setup infrastructure, provision VNFs""" + self.map_topology_to_infrastructure() + self.load_vnf_models() + + traffic_runners = [vnf for vnf in self.vnfs if vnf.runs_traffic] + non_traffic_runners = [vnf for vnf in self.vnfs if not vnf.runs_traffic] try: - try: - self.collector.stop() - for vnf in self.vnfs: - LOG.info("Stopping %s", vnf.name) - vnf.terminate() - LOG.debug("all VNFs terminated: %s", ", ".join(vnf.name for vnf in self.vnfs)) - finally: - terminate_children() - except Exception: - # catch any exception in teardown and convert to simple exception - # never pass exceptions back to multiprocessing, because some exceptions can - # be unpicklable - # https://bugs.python.org/issue9400 + for vnf in chain(traffic_runners, non_traffic_runners): + LOG.info("Instantiating %s", vnf.name) + vnf.instantiate(self.scenario_cfg, self.context_cfg) + LOG.info("Waiting for %s to instantiate", vnf.name) + vnf.wait_for_instantiate() + except: LOG.exception("") - raise RuntimeError("Error in teardown") + for vnf in self.vnfs: + vnf.terminate() + raise + + self._generate_pod_yaml() + + def run(self, output): + """ Run experiment + + :param output: scenario output to push results + :return: None + """ + + self._fill_traffic_profile() + + traffic_runners = [vnf for vnf in self.vnfs if vnf.runs_traffic] + + for traffic_gen in traffic_runners: + traffic_gen.listen_traffic(self.traffic_profile) + + self.collector = Collector(self.vnfs, + context_base.Context.get_physical_nodes()) + self.collector.start() + + for traffic_gen in traffic_runners: + LOG.info("Run traffic on %s", traffic_gen.name) + traffic_gen.run_traffic(self.traffic_profile) + + output.push(self.collector.get_kpi()) + + self.collector.stop() diff --git a/yardstick/benchmark/scenarios/networking/vsperf.py b/yardstick/benchmark/scenarios/networking/vsperf.py index 705544c41..8344b1595 100644 --- a/yardstick/benchmark/scenarios/networking/vsperf.py +++ b/yardstick/benchmark/scenarios/networking/vsperf.py @@ -193,37 +193,34 @@ class Vsperf(base.Scenario): cmd += "--conf-file ~/vsperf.conf " cmd += "--test-params=\"%s\"" % (';'.join(test_params)) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) - - if status: - raise RuntimeError(stderr) + self.client.run(cmd) # get test results cmd = "cat /tmp/results*/result.csv" LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) - - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.client.execute(cmd, raise_on_error=True) # convert result.csv to JSON format - reader = csv.DictReader(stdout.split('\r\n')) - result.update(next(reader)) + reader = csv.DictReader(stdout.split('\r\n'), strict=True) + try: + result.update(next(reader)) + except StopIteration: + pass # sla check; go through all defined SLAs and check if values measured # by VSPERF are higher then those defined by SLAs if 'sla' in self.scenario_cfg and \ 'metrics' in self.scenario_cfg['sla']: for metric in self.scenario_cfg['sla']['metrics'].split(','): - assert metric in result, \ - '%s is not collected by VSPERF' % (metric) - assert metric in self.scenario_cfg['sla'], \ - '%s is not defined in SLA' % (metric) + self.verify_SLA(metric in result, + '%s was not collected by VSPERF' % metric) + self.verify_SLA(metric in self.scenario_cfg['sla'], + '%s is not defined in SLA' % metric) vs_res = float(result[metric]) sla_res = float(self.scenario_cfg['sla'][metric]) - assert vs_res >= sla_res, \ - 'VSPERF_%s(%f) < SLA_%s(%f)' % \ - (metric, vs_res, metric, sla_res) + self.verify_SLA(vs_res >= sla_res, + 'VSPERF_%s(%f) < SLA_%s(%f)' + % (metric, vs_res, metric, sla_res)) def teardown(self): """cleanup after the test execution""" diff --git a/yardstick/benchmark/scenarios/networking/vsperf_dpdk.py b/yardstick/benchmark/scenarios/networking/vsperf_dpdk.py index 454587829..d5c8a3bfe 100644 --- a/yardstick/benchmark/scenarios/networking/vsperf_dpdk.py +++ b/yardstick/benchmark/scenarios/networking/vsperf_dpdk.py @@ -205,22 +205,17 @@ class VsperfDPDK(base.Scenario): self.client.send_command(cmd) else: cmd = "cat ~/.testpmd.macaddr.port1" - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.client.execute(cmd, raise_on_error=True) self.tgen_port1_mac = stdout + cmd = "cat ~/.testpmd.macaddr.port2" - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.client.execute(cmd, raise_on_error=True) self.tgen_port2_mac = stdout cmd = "screen -d -m sudo -E bash ~/testpmd_vsperf.sh %s %s" % \ (self.moongen_port1_mac, self.moongen_port2_mac) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.client.run(cmd) time.sleep(1) @@ -231,7 +226,7 @@ class VsperfDPDK(base.Scenario): is_run = True cmd = "ip a | grep %s 2>/dev/null" % (self.tg_port1) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) + _, stdout, _ = self.client.execute(cmd) if stdout: is_run = False return is_run @@ -245,7 +240,7 @@ class VsperfDPDK(base.Scenario): self.setup() # remove results from previous tests - self.client.execute("rm -rf /tmp/results*") + self.client.run("rm -rf /tmp/results*", raise_on_error=False) # get vsperf options options = self.scenario_cfg['options'] @@ -291,9 +286,7 @@ class VsperfDPDK(base.Scenario): cmd = "sshpass -p yardstick ssh-copy-id -o StrictHostKeyChecking=no " \ "root@%s -p 22" % (self.moongen_host_ip) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) - if status: - raise RuntimeError(stderr) + self.client.run(cmd) # execute vsperf cmd = "source ~/vsperfenv/bin/activate ; cd vswitchperf ; " @@ -302,22 +295,19 @@ class VsperfDPDK(base.Scenario): cmd += "--conf-file ~/vsperf.conf " cmd += "--test-params=\"%s\"" % (';'.join(test_params)) LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) - - if status: - raise RuntimeError(stderr) + self.client.run(cmd) # get test results cmd = "cat /tmp/results*/result.csv" LOG.debug("Executing command: %s", cmd) - status, stdout, stderr = self.client.execute(cmd) - - if status: - raise RuntimeError(stderr) + _, stdout, _ = self.client.execute(cmd, raise_on_error=True) # convert result.csv to JSON format reader = csv.DictReader(stdout.split('\r\n')) - result.update(next(reader)) + try: + result.update(next(reader)) + except StopIteration: + pass result['nrFlows'] = multistream # sla check; go through all defined SLAs and check if values measured @@ -325,15 +315,15 @@ class VsperfDPDK(base.Scenario): if 'sla' in self.scenario_cfg and \ 'metrics' in self.scenario_cfg['sla']: for metric in self.scenario_cfg['sla']['metrics'].split(','): - assert metric in result, \ - '%s is not collected by VSPERF' % (metric) - assert metric in self.scenario_cfg['sla'], \ - '%s is not defined in SLA' % (metric) + self.verify_SLA(metric in result, + '%s was not collected by VSPERF' % metric) + self.verify_SLA(metric in self.scenario_cfg['sla'], + '%s is not defined in SLA' % metric) vs_res = float(result[metric]) sla_res = float(self.scenario_cfg['sla'][metric]) - assert vs_res >= sla_res, \ - 'VSPERF_%s(%f) < SLA_%s(%f)' % \ - (metric, vs_res, metric, sla_res) + self.verify_SLA(vs_res >= sla_res, + 'VSPERF_%s(%f) < SLA_%s(%f)' + % (metric, vs_res, metric, sla_res)) def teardown(self): """cleanup after the test execution""" diff --git a/yardstick/benchmark/scenarios/parser/parser.py b/yardstick/benchmark/scenarios/parser/parser.py index eb16833e5..a0f8e9e72 100644 --- a/yardstick/benchmark/scenarios/parser/parser.py +++ b/yardstick/benchmark/scenarios/parser/parser.py @@ -6,13 +6,13 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from __future__ import print_function -from __future__ import absolute_import + import pkg_resources import logging import subprocess from yardstick.benchmark.scenarios import base + LOG = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class Parser(base.Scenario): """running Parser Yang-to-Tosca module as a tool validating output against expected outcome - more info https://wiki.opnfv.org/parser + more info https://wiki.opnfv.org/display/parser """ __scenario_type__ = "Parser" @@ -63,7 +63,7 @@ class Parser(base.Scenario): p = subprocess.Popen(cmd1, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate() - print("yangtotosca finished") + LOG.info("yangtotosca finished") result['yangtotosca'] = "success" if p.returncode == 0 else "fail" diff --git a/yardstick/benchmark/scenarios/storage/fio.py b/yardstick/benchmark/scenarios/storage/fio.py index 125bc7ed4..c57c6edf2 100644 --- a/yardstick/benchmark/scenarios/storage/fio.py +++ b/yardstick/benchmark/scenarios/storage/fio.py @@ -124,12 +124,16 @@ class Fio(base.Scenario): if mount_dir: LOG.debug("Formating volume...") - self.client.execute("sudo mkfs.ext4 /dev/vdb") - cmd = "sudo mkdir %s" % mount_dir - self.client.execute(cmd) - LOG.debug("Mounting volume at: %s", mount_dir) - cmd = "sudo mount /dev/vdb %s" % mount_dir - self.client.execute(cmd) + _, stdout, _ = self.client.execute( + "lsblk -dps | grep -m 1 disk | awk '{print $1}'") + block_device = stdout.strip() + if block_device: + self.client.execute("sudo mkfs.ext4 %s" % block_device) + cmd = "sudo mkdir %s" % mount_dir + self.client.execute(cmd) + LOG.debug("Mounting volume at: %s", mount_dir) + cmd = "sudo mount %s %s" % (block_device, mount_dir) + self.client.execute(cmd) self.setup_done = True @@ -219,7 +223,7 @@ class Fio(base.Scenario): sla_error += "%s %d < " \ "sla:%s(%d); " % (k, v, k, min_v) - assert sla_error == "", sla_error + self.verify_SLA(sla_error == "", sla_error) def _test(): diff --git a/yardstick/benchmark/scenarios/storage/storperf.py b/yardstick/benchmark/scenarios/storage/storperf.py index f0b2361d6..5b8b00075 100644 --- a/yardstick/benchmark/scenarios/storage/storperf.py +++ b/yardstick/benchmark/scenarios/storage/storperf.py @@ -8,15 +8,16 @@ ############################################################################## from __future__ import absolute_import -import os import logging +import os import time -import requests from oslo_serialization import jsonutils +import requests from yardstick.benchmark.scenarios import base + LOG = logging.getLogger(__name__) @@ -43,12 +44,6 @@ class StorPerf(base.Scenario): wr: 100% Write, random access rw: 70% Read / 30% write, random access - nossd (Optional): - Do not perform SSD style preconditioning. - - nowarm (Optional): - Do not perform a warmup prior to measurements. - report = [job_id] (Optional): Query the status of the supplied job_id and report on metrics. If a workload is supplied, will report on only that subset. @@ -79,17 +74,22 @@ class StorPerf(base.Scenario): setup_query_content = jsonutils.loads( setup_query.content) - if setup_query_content["stack_created"]: - self.setup_done = True + if ("stack_created" in setup_query_content and + setup_query_content["stack_created"]): LOG.debug("stack_created: %s", setup_query_content["stack_created"]) + return True + + return False def setup(self): """Set the configuration.""" env_args = {} env_args_payload_list = ["agent_count", "agent_flavor", "public_network", "agent_image", - "volume_size"] + "volume_size", "volume_type", + "volume_count", "availability_zone", + "stack_name", "subnet_CIDR"] for env_argument in env_args_payload_list: try: @@ -102,30 +102,36 @@ class StorPerf(base.Scenario): setup_res = requests.post('http://%s:5000/api/v1.0/configurations' % self.target, json=env_args) - setup_res_content = jsonutils.loads( - setup_res.content) if setup_res.status_code != 200: - raise RuntimeError("Failed to create a stack, error message:", - setup_res_content["message"]) + LOG.error("Failed to create stack. %s: %s", + setup_res.status_code, setup_res.content) + raise RuntimeError("Failed to create stack. %s: %s" % + (setup_res.status_code, setup_res.content)) elif setup_res.status_code == 200: + setup_res_content = jsonutils.loads(setup_res.content) LOG.info("stack_id: %s", setup_res_content["stack_id"]) - while not self.setup_done: - self._query_setup_state() - time.sleep(self.query_interval) + while not self._query_setup_state(): + time.sleep(self.query_interval) + + # We do not want to load the results of the disk initialization, + # so it is not added to the results here. + self.initialize_disks() + self.setup_done = True def _query_job_state(self, job_id): """Query the status of the supplied job_id and report on metrics""" LOG.info("Fetching report for %s...", job_id) - report_res = requests.get('http://{}:5000/api/v1.0/jobs'.format - (self.target), + report_res = requests.get('http://%s:5000/api/v1.0/jobs' % self.target, params={'id': job_id, 'type': 'status'}) report_res_content = jsonutils.loads( report_res.content) if report_res.status_code != 200: + LOG.error("Failed to fetch report, error message: %s", + report_res_content["message"]) raise RuntimeError("Failed to fetch report, error message:", report_res_content["message"]) else: @@ -149,7 +155,8 @@ class StorPerf(base.Scenario): if not self.setup_done: self.setup() - metadata = {"build_tag": "latest", "test_case": "opnfv_yardstick_tc074"} + metadata = {"build_tag": "latest", + "test_case": "opnfv_yardstick_tc074"} metadata_payload_dict = {"pod_name": "NODE_NAME", "scenario_name": "DEPLOY_SCENARIO", "version": "YARDSTICK_BRANCH"} @@ -162,7 +169,9 @@ class StorPerf(base.Scenario): job_args = {"metadata": metadata} job_args_payload_list = ["block_sizes", "queue_depths", "deadline", - "target", "nossd", "nowarm", "workload"] + "target", "workload", "workloads", + "agent_count", "steady_state_samples"] + job_args["deadline"] = self.options["timeout"] for job_argument in job_args_payload_list: try: @@ -170,16 +179,24 @@ class StorPerf(base.Scenario): except KeyError: pass - LOG.info("Starting a job with parameters %s", job_args) - job_res = requests.post('http://%s:5000/api/v1.0/jobs' % self.target, - json=job_args) + api_version = "v1.0" - job_res_content = jsonutils.loads(job_res.content) + if ("workloads" in job_args and + job_args["workloads"] is not None and + len(job_args["workloads"])) > 0: + api_version = "v2.0" + + LOG.info("Starting a job with parameters %s", job_args) + job_res = requests.post('http://%s:5000/api/%s/jobs' % (self.target, + api_version), json=job_args) if job_res.status_code != 200: - raise RuntimeError("Failed to start a job, error message:", - job_res_content["message"]) + LOG.error("Failed to start job. %s: %s", + job_res.status_code, job_res.content) + raise RuntimeError("Failed to start job. %s: %s" % + (job_res.status_code, job_res.content)) elif job_res.status_code == 200: + job_res_content = jsonutils.loads(job_res.content) job_id = job_res_content["job_id"] LOG.info("Started job id: %s...", job_id) @@ -187,15 +204,6 @@ class StorPerf(base.Scenario): self._query_job_state(job_id) time.sleep(self.query_interval) - terminate_res = requests.delete('http://%s:5000/api/v1.0/jobs' % - self.target) - - if terminate_res.status_code != 200: - terminate_res_content = jsonutils.loads( - terminate_res.content) - raise RuntimeError("Failed to start a job, error message:", - terminate_res_content["message"]) - # TODO: Support using ETA to polls for completion. # Read ETA, next poll in 1/2 ETA time slot. # If ETA is greater than the maximum allowed job time, @@ -209,21 +217,65 @@ class StorPerf(base.Scenario): # else: # time.sleep(int(esti_time)/2) + result_res = requests.get('http://%s:5000/api/v1.0/jobs?type=' + 'metadata&id=%s' % (self.target, job_id)) + result_res_content = jsonutils.loads(result_res.content) + if 'report' in result_res_content and \ + 'steady_state' in result_res_content['report']['details']: + res = result_res_content['report']['details']['steady_state'] + steady_state = res.values()[0] + LOG.info("Job %s completed with steady state %s", + job_id, steady_state) + result_res = requests.get('http://%s:5000/api/v1.0/jobs?id=%s' % (self.target, job_id)) result_res_content = jsonutils.loads( result_res.content) - result.update(result_res_content) + def initialize_disks(self): + """Fills the target with random data prior to executing workloads""" + + job_args = {} + job_args_payload_list = ["target"] + + for job_argument in job_args_payload_list: + try: + job_args[job_argument] = self.options[job_argument] + except KeyError: + pass + + LOG.info("Starting initialization with parameters %s", job_args) + job_res = requests.post('http://%s:5000/api/v1.0/initializations' % + self.target, json=job_args) + + + if job_res.status_code != 200: + LOG.error("Failed to start initialization job, error message: %s: %s", + job_res.status_code, job_res.content) + raise RuntimeError("Failed to start initialization job, error message: %s: %s" % + (job_res.status_code, job_res.content)) + elif job_res.status_code == 200: + job_res_content = jsonutils.loads(job_res.content) + job_id = job_res_content["job_id"] + LOG.info("Started initialization as job id: %s...", job_id) + + while not self.job_completed: + self._query_job_state(job_id) + time.sleep(self.query_interval) + + self.job_completed = False + def teardown(self): """Deletes the agent configuration and the stack""" - teardown_res = requests.delete('http://%s:5000/api/v1.0/\ - configurations' % self.target) + teardown_res = requests.delete( + 'http://%s:5000/api/v1.0/configurations' % self.target) if teardown_res.status_code == 400: teardown_res_content = jsonutils.loads( - teardown_res.content) + teardown_res.json_data) + LOG.error("Failed to reset environment, error message: %s", + teardown_res_content['message']) raise RuntimeError("Failed to reset environment, error message:", teardown_res_content['message']) |