aboutsummaryrefslogtreecommitdiffstats
path: root/yardstick
diff options
context:
space:
mode:
Diffstat (limited to 'yardstick')
-rw-r--r--yardstick/benchmark/scenarios/compute/spec_cpu_for_vm.py170
-rw-r--r--yardstick/common/exceptions.py40
-rw-r--r--yardstick/network_services/vnf_generic/vnf/acl_vnf.py3
-rw-r--r--yardstick/network_services/vnf_generic/vnf/base.py58
-rw-r--r--yardstick/network_services/vnf_generic/vnf/prox_helpers.py54
-rw-r--r--yardstick/network_services/vnf_generic/vnf/sample_vnf.py36
-rw-r--r--yardstick/network_services/vnf_generic/vnf/tg_ixload.py7
-rw-r--r--yardstick/network_services/vnf_generic/vnf/tg_ping.py4
-rw-r--r--yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py5
-rw-r--r--yardstick/network_services/vnf_generic/vnf/tg_trex.py8
-rw-r--r--yardstick/tests/__init__.py0
-rw-r--r--yardstick/tests/functional/__init__.py0
-rwxr-xr-xyardstick/tests/functional/test_cli_runner.py47
-rwxr-xr-xyardstick/tests/functional/test_cli_scenario.py54
-rwxr-xr-xyardstick/tests/functional/utils.py45
-rw-r--r--yardstick/tests/unit/__init__.py76
-rw-r--r--yardstick/tests/unit/apiserver/__init__.py58
-rw-r--r--yardstick/tests/unit/apiserver/resources/__init__.py0
-rw-r--r--yardstick/tests/unit/apiserver/resources/test_env_action.py43
-rw-r--r--yardstick/tests/unit/apiserver/utils/test_influx.py60
-rw-r--r--yardstick/tests/unit/common/__init__.py0
-rw-r--r--yardstick/tests/unit/common/config_sample.yaml10
-rw-r--r--yardstick/tests/unit/common/test_ansible_common.py248
-rw-r--r--yardstick/tests/unit/common/test_httpClient.py43
-rw-r--r--yardstick/tests/unit/common/test_openstack_utils.py46
-rw-r--r--yardstick/tests/unit/common/test_process.py150
-rw-r--r--yardstick/tests/unit/common/test_template_format.py54
-rw-r--r--yardstick/tests/unit/common/test_utils.py1085
-rw-r--r--yardstick/tests/unit/common/test_yaml_loader.py32
-rw-r--r--yardstick/tests/unit/test_ssh.py572
30 files changed, 2913 insertions, 95 deletions
diff --git a/yardstick/benchmark/scenarios/compute/spec_cpu_for_vm.py b/yardstick/benchmark/scenarios/compute/spec_cpu_for_vm.py
new file mode 100644
index 000000000..36489b132
--- /dev/null
+++ b/yardstick/benchmark/scenarios/compute/spec_cpu_for_vm.py
@@ -0,0 +1,170 @@
+##############################################################################
+# Copyright (c) 2017 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
+##############################################################################
+from __future__ import absolute_import
+
+import logging
+import pkg_resources
+import os
+
+import yardstick.ssh as ssh
+from yardstick.benchmark.scenarios import base
+from yardstick.common.constants import YARDSTICK_ROOT_PATH
+
+LOG = logging.getLogger(__name__)
+
+
+class SpecCPUforVM(base.Scenario):
+ """Spec CPU2006 benchmark for Virtual Machine
+
+ Parameters
+ benchmark_subset - Specifies a subset of SPEC CPU2006 benchmarks to run
+ type: string
+ unit: na
+ default: na
+
+ SPECint_benchmark - A SPECint benchmark to run
+ type: string
+ unit: na
+ default: na
+
+ SPECfp_benchmark - A SPECfp benchmark to run
+ type: string
+ unit: na
+ default: na
+
+ output_format - Desired report format
+ type: string
+ unit: na
+ default: na
+
+ runspec_config - SPEC CPU2006 config file provided to the runspec binary
+ type: string
+ unit: na
+ default: "Example-linux64-amd64-gcc43+.cfg"
+
+ runspec_iterations - The number of benchmark iterations to execute.
+ For a reportable run, must be 3.
+ type: int
+ unit: na
+ default: na
+
+ runspec_tune - Tuning to use (base, peak, or all). For a reportable run, must be either
+ base or all. Reportable runs do base first, then (optionally) peak.
+ type: string
+ unit: na
+ default: na
+
+ runspec_size - Size of input data to run (test, train, or ref). Reportable runs ensure
+ that your binaries can produce correct results with the test and train
+ workloads.
+ type: string
+ unit: na
+ default: na
+ """
+ __scenario_type__ = "SpecCPU2006_for_VM"
+ CPU2006_ISO = "cpu2006-1.2.iso"
+ CPU2006_DIR = "~/cpu2006"
+ CPU2006_RESULT_FILE = os.path.join(CPU2006_DIR, "result/CINT2006.001.ref.txt")
+
+ def __init__(self, scenario_cfg, context_cfg):
+ self.scenario_cfg = scenario_cfg
+ self.context_cfg = context_cfg
+ self.setup_done = False
+ self.options = self.scenario_cfg['options']
+
+ def setup(self):
+ """scenario setup"""
+ host = self.context_cfg['host']
+ LOG.info("user:%s, host:%s", host['user'], host['ip'])
+ self.client = ssh.SSH.from_node(host, defaults={"user": "ubuntu"})
+ self.client.wait(timeout=600)
+
+ spec_cpu_iso = os.path.join(YARDSTICK_ROOT_PATH,
+ "yardstick/resources/files/",
+ self.CPU2006_ISO)
+
+ self.client.put(spec_cpu_iso, "~/cpu2006-1.2.iso")
+ self.client.execute("sudo mount -t iso9660 -o ro,exec ~/cpu2006-1.2.iso /mnt")
+ self.client.execute("/mnt/install.sh -fd ~/cpu2006")
+
+ if "runspec_config" in self.options:
+ self.runspec_config = self.options["runspec_config"]
+
+ self.runspec_config_file = pkg_resources.resource_filename(
+ "yardstick.resources", 'files/' + self.runspec_config)
+
+ # copy SPEC CPU2006 config file to host if given
+ cfg_path = os.path.join(self.CPU2006_DIR,
+ 'config/yardstick_spec_cpu2006.cfg')
+ self.client._put_file_shell(self.runspec_config_file, cfg_path)
+ else:
+ self.runspec_config = "Example-linux64-amd64-gcc43+.cfg"
+
+ self.setup_done = True
+
+ def run(self, result):
+ """execute the benchmark"""
+
+ if not self.setup_done:
+ self.setup()
+
+ cmd = "cd %s && . ./shrc && runspec --config %s" % (
+ self.CPU2006_DIR, self.runspec_config)
+ cmd_args = ""
+
+ if "rate" in self.options:
+ cmd_args += " --rate %s" % self.options["runspec_rate"]
+
+ if "output_format" in self.options:
+ cmd_args += " --output_format %s" % self.options["output_format"]
+
+ if "runspec_tune" in self.options:
+ cmd_args += " --tune %s" % self.options["runspec_tune"]
+
+ benchmark_subset = self.options.get('benchmark_subset', None)
+ specint_benchmark = self.options.get('SPECint_benchmark', None)
+ specfp_benchmark = self.options.get('SPECfp_benchmark', None)
+
+ if benchmark_subset:
+ cmd_args += " %s" % benchmark_subset
+ else:
+ cmd_args += " --noreportable"
+
+ if "runspec_iterations" in self.options:
+ cmd_args += " --iterations %s" % self.options["runspec_iterations"]
+
+ if "runspec_size" in self.options:
+ cmd_args += " --size %s" % self.options["runspec_size"]
+
+ if specint_benchmark:
+ cmd_args += " %s" % specint_benchmark
+
+ if specfp_benchmark:
+ cmd_args += " %s" % specfp_benchmark
+
+ cmd += "%s" % cmd_args
+
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.client.execute(cmd, timeout=86400)
+ if status:
+ raise RuntimeError(stderr)
+
+ cmd = "cat %s" % self.CPU2006_RESULT_FILE
+ LOG.debug("Executing command: %s", cmd)
+ status, stdout, stderr = self.client.execute(cmd, timeout=30)
+ if status:
+ raise RuntimeError(stderr)
+ if stdout:
+ LOG.info("SPEC CPU2006 result is:\n%s", stdout)
+
+ result.update({"SPEC_CPU_result": stdout})
+ # fetch SPEC CPU2006 result files
+ self.client.get('~/cpu2006/result', '/tmp/')
+ LOG.info('SPEC CPU2006 benchmark completed, please find benchmark reports \
+ at /tmp/result directory')
diff --git a/yardstick/common/exceptions.py b/yardstick/common/exceptions.py
index 9c0ec2c19..4780822a4 100644
--- a/yardstick/common/exceptions.py
+++ b/yardstick/common/exceptions.py
@@ -12,8 +12,48 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from oslo_utils import excutils
+
class ProcessExecutionError(RuntimeError):
def __init__(self, message, returncode):
super(ProcessExecutionError, self).__init__(message)
self.returncode = returncode
+
+
+class YardstickException(Exception):
+ """Base Yardstick Exception.
+
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ Based on NeutronException class.
+ """
+ message = "An unknown exception occurred."
+
+ def __init__(self, **kwargs):
+ try:
+ super(YardstickException, self).__init__(self.message % kwargs)
+ self.msg = self.message % kwargs
+ except Exception: # pylint: disable=broad-except
+ with excutils.save_and_reraise_exception() as ctxt:
+ if not self.use_fatal_exceptions():
+ ctxt.reraise = False
+ # at least get the core message out if something happened
+ super(YardstickException, self).__init__(self.message)
+
+ def __str__(self):
+ return self.msg
+
+ def use_fatal_exceptions(self):
+ """Is the instance using fatal exceptions.
+
+ :returns: Always returns False.
+ """
+ return False
+
+
+class FunctionNotImplemented(YardstickException):
+ message = ('The function "%(function_name)s" is not implemented in '
+ '"%(class_name)" class.')
diff --git a/yardstick/network_services/vnf_generic/vnf/acl_vnf.py b/yardstick/network_services/vnf_generic/vnf/acl_vnf.py
index 3ba38dec2..1390dd02e 100644
--- a/yardstick/network_services/vnf_generic/vnf/acl_vnf.py
+++ b/yardstick/network_services/vnf_generic/vnf/acl_vnf.py
@@ -61,9 +61,6 @@ class AclApproxVnf(SampleVNF):
super(AclApproxVnf, self).__init__(name, vnfd, setup_env_helper_type, resource_helper_type)
self.acl_rules = None
- def scale(self, flavor=""):
- raise NotImplementedError
-
def _start_vnf(self):
yang_model_path = find_relative_file(self.scenario_helper.options['rules'],
self.scenario_helper.task_path)
diff --git a/yardstick/network_services/vnf_generic/vnf/base.py b/yardstick/network_services/vnf_generic/vnf/base.py
index 8ed754dce..a776b0989 100644
--- a/yardstick/network_services/vnf_generic/vnf/base.py
+++ b/yardstick/network_services/vnf_generic/vnf/base.py
@@ -138,76 +138,62 @@ class VnfdHelper(dict):
yield port_name, port_num
-class VNFObject(object):
+@six.add_metaclass(abc.ABCMeta)
+class GenericVNF(object):
+ """Class providing file-like API for generic VNF implementation
+
+ Currently the only class implementing this interface is
+ yardstick/network_services/vnf_generic/vnf/sample_vnf:SampleVNF.
+ """
# centralize network naming convention
UPLINK = PortPairs.UPLINK
DOWNLINK = PortPairs.DOWNLINK
def __init__(self, name, vnfd):
- super(VNFObject, self).__init__()
self.name = name
- self.vnfd_helper = VnfdHelper(vnfd) # fixme: parse this into a structure
-
-
-class GenericVNF(VNFObject):
-
- """ Class providing file-like API for generic VNF implementation """
- def __init__(self, name, vnfd):
- super(GenericVNF, self).__init__(name, vnfd)
+ self.vnfd_helper = VnfdHelper(vnfd)
# List of statistics we can obtain from this VNF
# - ETSI MANO 6.3.1.1 monitoring_parameter
- self.kpi = self._get_kpi_definition()
+ self.kpi = self.vnfd_helper.kpi
# Standard dictionary containing params like thread no, buffer size etc
self.config = {}
self.runs_traffic = False
- def _get_kpi_definition(self):
- """ Get list of KPIs defined in VNFD
-
- :param vnfd:
- :return: list of KPIs, e.g. ['throughput', 'latency']
- """
- return self.vnfd_helper.kpi
-
+ @abc.abstractmethod
def instantiate(self, scenario_cfg, context_cfg):
- """ Prepare VNF for operation and start the VNF process/VM
+ """Prepare VNF for operation and start the VNF process/VM
- :param scenario_cfg:
- :param context_cfg:
+ :param scenario_cfg: Scenario config
+ :param context_cfg: Context config
:return: True/False
"""
- raise NotImplementedError()
+ @abc.abstractmethod
def wait_for_instantiate(self):
- """ Wait for VNF to start
+ """Wait for VNF to start
:return: True/False
"""
- raise NotImplementedError()
+ @abc.abstractmethod
def terminate(self):
- """ Kill all VNF processes
-
- :return:
- """
- raise NotImplementedError()
+ """Kill all VNF processes"""
+ @abc.abstractmethod
def scale(self, flavor=""):
- """
+ """rest
- :param flavor:
+ :param flavor: Name of the flavor.
:return:
"""
- raise NotImplementedError()
+ @abc.abstractmethod
def collect_kpi(self):
- """This method should return a dictionary containing the
- selected KPI at a given point of time.
+ """Return a dict containing the selected KPI at a given point of time
:return: {"kpi": value, "kpi2": value}
"""
- raise NotImplementedError()
@six.add_metaclass(abc.ABCMeta)
diff --git a/yardstick/network_services/vnf_generic/vnf/prox_helpers.py b/yardstick/network_services/vnf_generic/vnf/prox_helpers.py
index ba066333d..285ead3b6 100644
--- a/yardstick/network_services/vnf_generic/vnf/prox_helpers.py
+++ b/yardstick/network_services/vnf_generic/vnf/prox_helpers.py
@@ -11,7 +11,6 @@
# 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.
-from __future__ import absolute_import
import array
import io
@@ -363,9 +362,9 @@ class ProxSocketHelper(object):
""" send data to the remote instance """
LOG.debug("Sending data to socket: [%s]", to_send.rstrip('\n'))
try:
- # TODO: sendall will block, we need a timeout
+ # NOTE: sendall will block, we need a timeout
self._sock.sendall(to_send.encode('utf-8'))
- except:
+ except: # pylint: disable=bare-except
pass
def get_packet_dump(self):
@@ -539,7 +538,7 @@ class ProxSocketHelper(object):
finally:
container['end_tot'] = end = self.get_all_tot_stats()
- container['delta'] = TotStatsTuple(end - start for start, end in zip(start, end))
+ container['delta'] = TotStatsTuple(e - s for s, e in zip(start, end))
def tot_stats(self):
"""Get the total statistics from the remote system"""
@@ -637,13 +636,6 @@ class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
raise KeyError(template.format(section_key, section_name))
return result
- def _build_pipeline_kwargs(self):
- tool_path = self.ssh_helper.provision_tool(tool_file=self.APP_NAME)
- self.pipeline_kwargs = {
- 'tool_path': tool_path,
- 'tool_dir': os.path.dirname(tool_path),
- }
-
def copy_to_target(self, config_file_path, prox_file):
remote_path = os.path.join("/tmp", prox_file)
self.ssh_helper.put(config_file_path, remote_path)
@@ -685,14 +677,13 @@ class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
if port_section_name != section_name:
continue
- for index, section_data in enumerate(section):
+ for section_data in section:
if section_data[0] == "mac":
section_data[1] = "hardware"
# search for dst mac
for _, section in sections:
- # for index, (item_key, item_val) in enumerate(section):
- for index, section_data in enumerate(section):
+ for section_data in section:
item_key, item_val = section_data
if item_val.startswith("@@dst_mac"):
tx_port_iter = re.finditer(r'\d+', item_val)
@@ -713,14 +704,14 @@ class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
return sections
for section_name, section in sections:
- for index, section_data in enumerate(section):
+ for section_data in section:
try:
if section_data[0].startswith("dofile"):
section_data[0] = self._insert_additional_file(section_data[0])
if section_data[1].startswith("dofile"):
section_data[1] = self._insert_additional_file(section_data[1])
- except:
+ except: # pylint: disable=bare-except
pass
return sections
@@ -753,9 +744,9 @@ class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
a custom method
"""
out = []
- for i, (section_name, section) in enumerate(prox_config):
+ for (section_name, section) in prox_config:
out.append("[{}]".format(section_name))
- for index, item in enumerate(section):
+ for item in section:
key, value = item
if key == "__name__":
continue
@@ -816,7 +807,7 @@ class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
self.lua = self.generate_prox_lua_file()
if len(self.lua) > 0:
self.upload_prox_lua("parameters.lua", self.lua)
- except:
+ except: # pylint: disable=bare-except
pass
prox_files = options.get('prox_files', [])
@@ -837,17 +828,20 @@ class ProxDpdkVnfSetupEnvHelper(DpdkVnfSetupEnvHelper):
self.build_config_file()
options = self.scenario_helper.options
-
prox_args = options['prox_args']
- LOG.info("Provision and start the %s", self.APP_NAME)
- self._build_pipeline_kwargs()
- self.pipeline_kwargs["args"] = " ".join(
- " ".join([k, v if v else ""]) for k, v in prox_args.items())
- self.pipeline_kwargs["cfg_file"] = self.remote_path
+ tool_path = self.ssh_helper.join_bin_path(self.APP_NAME)
+
+ self.pipeline_kwargs = {
+ 'tool_path': tool_path,
+ 'tool_dir': os.path.dirname(tool_path),
+ 'cfg_file': self.remote_path,
+ 'args': ' '.join(' '.join([str(k), str(v) if v else ''])
+ for k, v in prox_args.items())
+ }
- cmd_template = "sudo bash -c 'cd {tool_dir}; {tool_path} -o cli {args} -f {cfg_file} '"
- prox_cmd = cmd_template.format(**self.pipeline_kwargs)
- return prox_cmd
+ cmd_template = ("sudo bash -c 'cd {tool_dir}; {tool_path} -o cli "
+ "{args} -f {cfg_file} '")
+ return cmd_template.format(**self.pipeline_kwargs)
# this might be bad, sometimes we want regular ResourceHelper methods, like collect_kpi
@@ -1057,7 +1051,7 @@ class ProxDataHelper(object):
self.tsc_hz = float(self.sut.hz())
def line_rate_to_pps(self):
- # FIXME Don't hardcode 10Gb/s
+ # NOTE: to fix, don't hardcode 10Gb/s
return self.port_count * TEN_GIGABIT / BITS_PER_BYTE / (self.pkt_size + 20)
@@ -1658,7 +1652,7 @@ class ProxlwAFTRProfileHelper(ProxProfileHelper):
tun_ports = []
inet_ports = []
- re_port = re.compile('port (\d+)')
+ re_port = re.compile(r'port (\d+)')
for section_name, section in self.resource_helper.setup_helper.prox_config_data:
match = re_port.search(section_name)
if not match:
diff --git a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py
index 20e5895ee..5eeb6c889 100644
--- a/yardstick/network_services/vnf_generic/vnf/sample_vnf.py
+++ b/yardstick/network_services/vnf_generic/vnf/sample_vnf.py
@@ -28,6 +28,7 @@ from six.moves import cStringIO
from yardstick.benchmark.contexts.base import Context
from yardstick.benchmark.scenarios.networking.vnf_generic import find_relative_file
+from yardstick.common import exceptions as y_exceptions
from yardstick.common.process import check_if_process_failed
from yardstick.network_services.helpers.samplevnf_helper import PortPairs
from yardstick.network_services.helpers.samplevnf_helper import MultiPortConfig
@@ -308,7 +309,7 @@ class DpdkVnfSetupEnvHelper(SetupEnvHelper):
if vpci == v['virtual-interface']['vpci'])
# force to int
intf['virtual-interface']['dpdk_port_num'] = int(dpdk_port_num)
- except:
+ except: # pylint: disable=bare-except
pass
time.sleep(2)
@@ -472,6 +473,11 @@ class ClientResourceHelper(ResourceHelper):
self.client.clear_stats(ports=ports)
def start(self, ports=None, *args, **kwargs):
+ # pylint: disable=keyword-arg-before-vararg
+ # NOTE(ralonsoh): defining keyworded arguments before variable
+ # positional arguments is a bug. This function definition doesn't work
+ # in Python 2, although it works in Python 3. Reference:
+ # https://www.python.org/dev/peps/pep-3102/
if ports is None:
ports = self.all_ports
self.client.start(ports=ports, *args, **kwargs)
@@ -480,8 +486,8 @@ class ClientResourceHelper(ResourceHelper):
if not self._queue.empty():
kpi = self._queue.get()
self._result.update(kpi)
- LOG.debug("Got KPIs from _queue for {0} {1}".format(
- self.scenario_helper.name, self.RESOURCE_WORD))
+ LOG.debug('Got KPIs from _queue for %s %s',
+ self.scenario_helper.name, self.RESOURCE_WORD)
return self._result
def _connect(self, client=None):
@@ -670,7 +676,7 @@ class SampleVNF(GenericVNF):
self.pipeline_kwargs = {}
self.uplink_ports = None
self.downlink_ports = None
- # TODO(esm): make QueueFileWrapper invert-able so that we
+ # NOTE(esm): make QueueFileWrapper invert-able so that we
# never have to manage the queues
self.q_in = Queue()
self.q_out = Queue()
@@ -751,7 +757,7 @@ class SampleVNF(GenericVNF):
if not self._vnf_process.is_alive():
raise RuntimeError("%s VNF process died." % self.APP_NAME)
- # TODO(esm): move to QueueFileWrapper
+ # NOTE(esm): move to QueueFileWrapper
while self.q_out.qsize() > 0:
buf.append(self.q_out.get())
message = ''.join(buf)
@@ -821,12 +827,12 @@ class SampleVNF(GenericVNF):
self._vnf_process.terminate()
# no terminate children here because we share processes with tg
- def get_stats(self, *args, **kwargs):
- """
- Method for checking the statistics
+ def get_stats(self, *args, **kwargs): # pylint: disable=unused-argument
+ """Method for checking the statistics
+
+ This method could be overridden in children classes.
- :return:
- VNF statistics
+ :return: VNF statistics
"""
cmd = 'p {0} stats'.format(self.APP_WORD)
out = self.vnf_execute(cmd)
@@ -849,6 +855,11 @@ class SampleVNF(GenericVNF):
LOG.debug("%s collect KPIs %s", self.APP_NAME, result)
return result
+ def scale(self, flavor=""):
+ """The SampleVNF base class doesn't provide the 'scale' feature"""
+ raise y_exceptions.FunctionNotImplemented(
+ function_name='scale', class_name='SampleVNFTrafficGen')
+
class SampleVNFTrafficGen(GenericTrafficGen):
""" Class providing file-like API for generic traffic generator """
@@ -964,3 +975,8 @@ class SampleVNFTrafficGen(GenericTrafficGen):
self._tg_process.join(PROCESS_JOIN_TIMEOUT)
self._tg_process.terminate()
# no terminate children here because we share processes with vnf
+
+ def scale(self, flavor=""):
+ """A traffic generator VFN doesn't provide the 'scale' feature"""
+ raise y_exceptions.FunctionNotImplemented(
+ function_name='scale', class_name='SampleVNFTrafficGen')
diff --git a/yardstick/network_services/vnf_generic/vnf/tg_ixload.py b/yardstick/network_services/vnf_generic/vnf/tg_ixload.py
index 61c045405..3ab30b53e 100644
--- a/yardstick/network_services/vnf_generic/vnf/tg_ixload.py
+++ b/yardstick/network_services/vnf_generic/vnf/tg_ixload.py
@@ -91,7 +91,7 @@ class IxLoadResourceHelper(ClientResourceHelper):
self.result[key].append(value)
def setup(self):
- # TODO: fixupt scenario_helper to hanlde ixia
+ # NOTE: fixup scenario_helper to hanlde ixia
self.resource_file_name = \
find_relative_file(self.scenario_helper.scenario_cfg['ixia_profile'],
self.scenario_helper.scenario_cfg["task_path"])
@@ -113,7 +113,7 @@ class IxLoadResourceHelper(ClientResourceHelper):
def collect_kpi(self):
if self.data:
self._result.update(self.data)
- LOG.info("Collect {0} KPIs {1}".format(self.RESOURCE_WORD, self._result))
+ LOG.info("Collect %s KPIs %s", self.RESOURCE_WORD, self._result)
return self._result
def log(self):
@@ -170,9 +170,6 @@ class IxLoadTrafficGen(SampleVNFTrafficGen):
self.resource_helper.log()
self.resource_helper.data = self.resource_helper.make_aggregates()
- def instantiate(self, scenario_cfg, context_cfg):
- super(IxLoadTrafficGen, self).instantiate(scenario_cfg, context_cfg)
-
def terminate(self):
call(["pkill", "-9", "http_ixload.py"])
super(IxLoadTrafficGen, self).terminate()
diff --git a/yardstick/network_services/vnf_generic/vnf/tg_ping.py b/yardstick/network_services/vnf_generic/vnf/tg_ping.py
index 5238a5f02..a989543f5 100644
--- a/yardstick/network_services/vnf_generic/vnf/tg_ping.py
+++ b/yardstick/network_services/vnf_generic/vnf/tg_ping.py
@@ -113,10 +113,6 @@ class PingTrafficGen(SampleVNFTrafficGen):
resource_helper_type)
self._result = {}
- def scale(self, flavor=""):
- """ scale vnf-based on flavor input """
- pass
-
def _check_status(self):
return self._tg_process.is_alive()
diff --git a/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py b/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py
index a8b19cfba..630c8b9c0 100644
--- a/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py
+++ b/yardstick/network_services/vnf_generic/vnf/tg_rfc2544_ixia.py
@@ -174,7 +174,7 @@ class IxiaResourceHelper(ClientResourceHelper):
break
self.client.ix_stop_traffic()
- except Exception:
+ except Exception: # pylint: disable=broad-except
LOG.exception("Run Traffic terminated")
self._terminated.value = 1
@@ -201,9 +201,6 @@ class IxiaTrafficGen(SampleVNFTrafficGen):
def _check_status(self):
pass
- def scale(self, flavor=""):
- pass
-
def terminate(self):
self.resource_helper.stop_collect()
super(IxiaTrafficGen, self).terminate()
diff --git a/yardstick/network_services/vnf_generic/vnf/tg_trex.py b/yardstick/network_services/vnf_generic/vnf/tg_trex.py
index 4250cb7a6..0084a124c 100644
--- a/yardstick/network_services/vnf_generic/vnf/tg_trex.py
+++ b/yardstick/network_services/vnf_generic/vnf/tg_trex.py
@@ -126,6 +126,11 @@ class TrexResourceHelper(ClientResourceHelper):
self.ssh_helper.execute(self.MAKE_INSTALL.format(ko_src))
def start(self, ports=None, *args, **kwargs):
+ # pylint: disable=keyword-arg-before-vararg
+ # NOTE(ralonsoh): defining keyworded arguments before variable
+ # positional arguments is a bug. This function definition doesn't work
+ # in Python 2, although it works in Python 3. Reference:
+ # https://www.python.org/dev/peps/pep-3102/
cmd = "sudo fuser -n tcp {0.SYNC_PORT} {0.ASYNC_PORT} -k > /dev/null 2>&1"
self.ssh_helper.execute(cmd.format(self))
@@ -186,9 +191,6 @@ class TrexTrafficGen(SampleVNFTrafficGen):
super(TrexTrafficGen, self)._start_server()
self.resource_helper.start()
- def scale(self, flavor=""):
- pass
-
def terminate(self):
self.resource_helper.terminate()
diff --git a/yardstick/tests/__init__.py b/yardstick/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/yardstick/tests/__init__.py
diff --git a/yardstick/tests/functional/__init__.py b/yardstick/tests/functional/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/yardstick/tests/functional/__init__.py
diff --git a/yardstick/tests/functional/test_cli_runner.py b/yardstick/tests/functional/test_cli_runner.py
new file mode 100755
index 000000000..2f2d7fef2
--- /dev/null
+++ b/yardstick/tests/functional/test_cli_runner.py
@@ -0,0 +1,47 @@
+##############################################################################
+# Copyright (c) 2015 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
+##############################################################################
+
+import unittest
+
+from yardstick.tests.functional import utils
+
+
+class RunnerTestCase(unittest.TestCase):
+
+ def setUp(self):
+ super(RunnerTestCase, self).setUp()
+ self.yardstick = utils.Yardstick()
+
+ def test_runner_list(self):
+ res = self.yardstick("runner list")
+
+ self.assertIn("Duration", res)
+ self.assertIn("Arithmetic", res)
+ self.assertIn("Iteration", res)
+ self.assertIn("Sequence", res)
+
+ def test_runner_show_Duration(self):
+ res = self.yardstick("runner show Duration")
+ duration = "duration - amount of time" in res
+ self.assertTrue(duration)
+
+ def test_runner_show_Arithmetic(self):
+ res = self.yardstick("runner show Arithmetic")
+ arithmetic = "Run a scenario arithmetically" in res
+ self.assertTrue(arithmetic)
+
+ def test_runner_show_Iteration(self):
+ res = self.yardstick("runner show Iteration")
+ iteration = "iterations - amount of times" in res
+ self.assertTrue(iteration)
+
+ def test_runner_show_Sequence(self):
+ res = self.yardstick("runner show Sequence")
+ sequence = "sequence - list of values which are executed" in res
+ self.assertTrue(sequence)
diff --git a/yardstick/tests/functional/test_cli_scenario.py b/yardstick/tests/functional/test_cli_scenario.py
new file mode 100755
index 000000000..7aaacad0a
--- /dev/null
+++ b/yardstick/tests/functional/test_cli_scenario.py
@@ -0,0 +1,54 @@
+##############################################################################
+# Copyright (c) 2015 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
+##############################################################################
+
+import unittest
+
+from yardstick.tests.functional import utils
+
+
+class ScenarioTestCase(unittest.TestCase):
+
+ def setUp(self):
+ super(ScenarioTestCase, self).setUp()
+ self.yardstick = utils.Yardstick()
+
+ def test_scenario_list(self):
+ res = self.yardstick("scenario list")
+
+ self.assertIn("Lmbench", res)
+ self.assertIn("Perf", res)
+ self.assertIn("Fio", res)
+ self.assertIn("Ping", res)
+ self.assertIn("Iperf3", res)
+ self.assertIn("Pktgen", res)
+
+ def test_scenario_show_Lmbench(self):
+ res = self.yardstick("scenario show Lmbench")
+ self.assertIn("Execute lmbench memory read latency or memory "
+ "bandwidth benchmark in a hos", res)
+
+ def test_scenario_show_Perf(self):
+ res = self.yardstick("scenario show Perf")
+ self.assertIn("Execute perf benchmark in a host", res)
+
+ def test_scenario_show_Fio(self):
+ res = self.yardstick("scenario show Fio")
+ self.assertIn("Execute fio benchmark in a host", res)
+
+ def test_scenario_show_Ping(self):
+ res = self.yardstick("scenario show Ping")
+ self.assertIn("Execute ping between two hosts", res)
+
+ def test_scenario_show_Iperf3(self):
+ res = self.yardstick("scenario show Iperf3")
+ self.assertIn("Execute iperf3 between two hosts", res)
+
+ def test_scenario_show_Pktgen(self):
+ res = self.yardstick("scenario show Pktgen")
+ self.assertIn("Execute pktgen between two hosts", res)
diff --git a/yardstick/tests/functional/utils.py b/yardstick/tests/functional/utils.py
new file mode 100755
index 000000000..d889c0dfa
--- /dev/null
+++ b/yardstick/tests/functional/utils.py
@@ -0,0 +1,45 @@
+##############################################################################
+# Copyright (c) 2015 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
+##############################################################################
+
+import copy
+import os
+
+from oslo_serialization import jsonutils
+
+from yardstick.common import process
+
+
+class Yardstick(object):
+ """Create and represent separate yardstick installation.
+
+ Usage:
+ yardstick = yardstick()
+ output = yardstick("runner list")
+
+ """
+
+ def __init__(self):
+ self._args = ["yardstick"]
+ self.env = copy.deepcopy(os.environ)
+
+ def __call__(self, cmd, getjson=False):
+ """Call yardstick in the shell
+
+ :param cmd: Yardstick command.
+ :param getjson: If the output is a JSON object, it's deserialized.
+ :return Command output string.
+ """
+
+ if not isinstance(cmd, list):
+ cmd = cmd.split(" ")
+ cmd = self._args + cmd
+ output = process.execute(cmd=cmd)
+ if getjson:
+ return jsonutils.loads(output)
+ return output
diff --git a/yardstick/tests/unit/__init__.py b/yardstick/tests/unit/__init__.py
new file mode 100644
index 000000000..a468b272b
--- /dev/null
+++ b/yardstick/tests/unit/__init__.py
@@ -0,0 +1,76 @@
+# Copyright (c) 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.
+
+from __future__ import absolute_import
+import mock
+
+
+STL_MOCKS = {
+ 'trex_stl_lib': mock.MagicMock(),
+ 'trex_stl_lib.base64': mock.MagicMock(),
+ 'trex_stl_lib.binascii': mock.MagicMock(),
+ 'trex_stl_lib.collections': mock.MagicMock(),
+ 'trex_stl_lib.copy': mock.MagicMock(),
+ 'trex_stl_lib.datetime': mock.MagicMock(),
+ 'trex_stl_lib.functools': mock.MagicMock(),
+ 'trex_stl_lib.imp': mock.MagicMock(),
+ 'trex_stl_lib.inspect': mock.MagicMock(),
+ 'trex_stl_lib.json': mock.MagicMock(),
+ 'trex_stl_lib.linecache': mock.MagicMock(),
+ 'trex_stl_lib.math': mock.MagicMock(),
+ 'trex_stl_lib.os': mock.MagicMock(),
+ 'trex_stl_lib.platform': mock.MagicMock(),
+ 'trex_stl_lib.pprint': mock.MagicMock(),
+ 'trex_stl_lib.random': mock.MagicMock(),
+ 'trex_stl_lib.re': mock.MagicMock(),
+ 'trex_stl_lib.scapy': mock.MagicMock(),
+ 'trex_stl_lib.socket': mock.MagicMock(),
+ 'trex_stl_lib.string': mock.MagicMock(),
+ 'trex_stl_lib.struct': mock.MagicMock(),
+ 'trex_stl_lib.sys': mock.MagicMock(),
+ 'trex_stl_lib.threading': mock.MagicMock(),
+ 'trex_stl_lib.time': mock.MagicMock(),
+ 'trex_stl_lib.traceback': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_async_client': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_client': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_exceptions': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_ext': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_jsonrpc_client': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_packet_builder_interface': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_packet_builder_scapy': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_port': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_stats': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_streams': mock.MagicMock(),
+ 'trex_stl_lib.trex_stl_types': mock.MagicMock(),
+ 'trex_stl_lib.types': mock.MagicMock(),
+ 'trex_stl_lib.utils': mock.MagicMock(),
+ 'trex_stl_lib.utils.argparse': mock.MagicMock(),
+ 'trex_stl_lib.utils.collections': mock.MagicMock(),
+ 'trex_stl_lib.utils.common': mock.MagicMock(),
+ 'trex_stl_lib.utils.json': mock.MagicMock(),
+ 'trex_stl_lib.utils.os': mock.MagicMock(),
+ 'trex_stl_lib.utils.parsing_opts': mock.MagicMock(),
+ 'trex_stl_lib.utils.pwd': mock.MagicMock(),
+ 'trex_stl_lib.utils.random': mock.MagicMock(),
+ 'trex_stl_lib.utils.re': mock.MagicMock(),
+ 'trex_stl_lib.utils.string': mock.MagicMock(),
+ 'trex_stl_lib.utils.sys': mock.MagicMock(),
+ 'trex_stl_lib.utils.text_opts': mock.MagicMock(),
+ 'trex_stl_lib.utils.text_tables': mock.MagicMock(),
+ 'trex_stl_lib.utils.texttable': mock.MagicMock(),
+ 'trex_stl_lib.warnings': mock.MagicMock(),
+ 'trex_stl_lib.yaml': mock.MagicMock(),
+ 'trex_stl_lib.zlib': mock.MagicMock(),
+ 'trex_stl_lib.zmq': mock.MagicMock(),
+}
diff --git a/yardstick/tests/unit/apiserver/__init__.py b/yardstick/tests/unit/apiserver/__init__.py
new file mode 100644
index 000000000..44d163429
--- /dev/null
+++ b/yardstick/tests/unit/apiserver/__init__.py
@@ -0,0 +1,58 @@
+##############################################################################
+# Copyright (c) 2017
+#
+# 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
+##############################################################################
+"""Tests for yardstick/api/server.py"""
+from __future__ import absolute_import
+
+import mock
+import os
+import socket
+import unittest
+import tempfile
+
+from oslo_serialization import jsonutils
+
+from yardstick.common import constants as consts
+
+
+class APITestCase(unittest.TestCase):
+ """Tests for the YardStick API server"""
+ def setUp(self):
+ self.db_fd, self.db_path = tempfile.mkstemp()
+ consts.SQLITE = 'sqlite:///{}'.format(self.db_path)
+
+ # server calls gethostbyname which takes 4 seconds, and we should mock
+ # it anyway
+ self.socket_mock = mock.patch.dict("sys.modules", {"socket": mock.MagicMock(
+ **{"gethostbyname.return_value": "127.0.0.1",
+ "gethostname.return_value": "localhost"})})
+ self.socket_mock.start()
+ try:
+ from api import server
+ except socket.gaierror:
+ self.app = None
+ return
+
+ server.app.config['TESTING'] = True
+ self.app = server.app.test_client()
+
+ server.init_db()
+
+ def tearDown(self):
+ os.close(self.db_fd)
+ os.unlink(self.db_path)
+ self.socket_mock.stop()
+
+ def _post(self, url, data):
+ headers = {'Content-Type': 'application/json'}
+ resp = self.app.post(url, data=jsonutils.dumps(data), headers=headers)
+ return jsonutils.loads(resp.data)
+
+ def _get(self, url):
+ resp = self.app.get(url)
+ return jsonutils.loads(resp.data)
diff --git a/yardstick/tests/unit/apiserver/resources/__init__.py b/yardstick/tests/unit/apiserver/resources/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/yardstick/tests/unit/apiserver/resources/__init__.py
diff --git a/yardstick/tests/unit/apiserver/resources/test_env_action.py b/yardstick/tests/unit/apiserver/resources/test_env_action.py
new file mode 100644
index 000000000..b7bfe294d
--- /dev/null
+++ b/yardstick/tests/unit/apiserver/resources/test_env_action.py
@@ -0,0 +1,43 @@
+##############################################################################
+# Copyright (c) 2017 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
+##############################################################################
+
+import time
+import unittest
+
+from yardstick.tests.unit.apiserver import APITestCase
+
+
+class EnvTestCase(APITestCase):
+
+ def test_create_grafana(self):
+ if self.app is None:
+ unittest.skip('host config error')
+ return
+
+ url = 'yardstick/env/action'
+ data = {'action': 'create_grafana'}
+ resp = self._post(url, data)
+
+ time.sleep(0)
+
+ task_id = resp['result']['task_id']
+ url = '/yardstick/asynctask?task_id={}'.format(task_id)
+ resp = self._get(url)
+
+ time.sleep(0)
+
+ self.assertTrue(u'status' in resp)
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/apiserver/utils/test_influx.py b/yardstick/tests/unit/apiserver/utils/test_influx.py
new file mode 100644
index 000000000..883608bb2
--- /dev/null
+++ b/yardstick/tests/unit/apiserver/utils/test_influx.py
@@ -0,0 +1,60 @@
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import unittest
+import mock
+
+from api.utils import influx
+from six.moves import configparser as ConfigParser
+
+
+class GetDataDbClientTestCase(unittest.TestCase):
+
+ @mock.patch('api.utils.influx.ConfigParser')
+ def test_get_data_db_client_dispatcher_not_influxdb(self, mock_parser):
+ mock_parser.ConfigParser().get.return_value = 'file'
+ # reset exception to avoid
+ # TypeError: catching classes that do not inherit from BaseException
+ mock_parser.NoOptionError = ConfigParser.NoOptionError
+ try:
+ influx.get_data_db_client()
+ except Exception as e: # pylint: disable=broad-except
+ self.assertIsInstance(e, RuntimeError)
+
+
+class GetIpTestCase(unittest.TestCase):
+
+ def test_get_url(self):
+ url = 'http://localhost:8086/hello'
+ output = influx._get_ip(url)
+
+ result = 'localhost'
+ self.assertEqual(result, output)
+
+
+class QueryTestCase(unittest.TestCase):
+
+ @mock.patch('api.utils.influx.ConfigParser')
+ def test_query_dispatcher_not_influxdb(self, mock_parser):
+ mock_parser.ConfigParser().get.return_value = 'file'
+ # reset exception to avoid
+ # TypeError: catching classes that do not inherit from BaseException
+ mock_parser.NoOptionError = ConfigParser.NoOptionError
+ try:
+ sql = 'select * form tasklist'
+ influx.query(sql)
+ except Exception as e: # pylint: disable=broad-except
+ self.assertIsInstance(e, RuntimeError)
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/common/__init__.py b/yardstick/tests/unit/common/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/yardstick/tests/unit/common/__init__.py
diff --git a/yardstick/tests/unit/common/config_sample.yaml b/yardstick/tests/unit/common/config_sample.yaml
new file mode 100644
index 000000000..09218cc79
--- /dev/null
+++ b/yardstick/tests/unit/common/config_sample.yaml
@@ -0,0 +1,10 @@
+##############################################################################
+# Copyright (c) 2017 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
+##############################################################################
+releng:
+ dir: /home/opnfv/repos/releng
diff --git a/yardstick/tests/unit/common/test_ansible_common.py b/yardstick/tests/unit/common/test_ansible_common.py
new file mode 100644
index 000000000..89ea128af
--- /dev/null
+++ b/yardstick/tests/unit/common/test_ansible_common.py
@@ -0,0 +1,248 @@
+# 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.
+
+
+from __future__ import absolute_import
+
+import os
+import tempfile
+from collections import defaultdict
+
+import mock
+import unittest
+
+from six.moves.configparser import ConfigParser
+from six.moves import StringIO
+
+from yardstick.common import ansible_common
+
+PREFIX = 'yardstick.common.ansible_common'
+
+
+class OverwriteDictTestCase(unittest.TestCase):
+ def test_overwrite_dict_cfg(self):
+ c = ConfigParser(allow_no_value=True)
+ d = {
+ "section_a": "empty_value",
+ "section_b": {"key_c": "Val_d", "key_d": "VAL_D"},
+ "section_c": ["key_c", "key_d"],
+ }
+ ansible_common.overwrite_dict_to_cfg(c, d)
+ # Python3 and Python2 convert empty values into None or ''
+ # we don't really care but we need to compare correctly for unittest
+ self.assertTrue(c.has_option("section_a", "empty_value"))
+ self.assertEqual(sorted(c.items("section_b")), [('key_c', 'Val_d'), ('key_d', 'VAL_D')])
+ self.assertTrue(c.has_option("section_c", "key_c"))
+ self.assertTrue(c.has_option("section_c", "key_d"))
+
+
+class FilenameGeneratorTestCase(unittest.TestCase):
+ @mock.patch('{}.NamedTemporaryFile'.format(PREFIX))
+ def test__handle_existing_file(self, _):
+ ansible_common.FileNameGenerator._handle_existing_file("/dev/null")
+
+ def test_get_generator_from_file(self):
+ ansible_common.FileNameGenerator.get_generator_from_filename("/dev/null", "", "", "")
+
+ def test_get_generator_from_file_middle(self):
+ ansible_common.FileNameGenerator.get_generator_from_filename("/dev/null", "", "",
+ "null")
+
+ def test_get_generator_from_file_prefix(self):
+ ansible_common.FileNameGenerator.get_generator_from_filename("/dev/null", "", "null",
+ "middle")
+
+
+class AnsibleNodeTestCase(unittest.TestCase):
+ def test_ansible_node(self):
+ ansible_common.AnsibleNode()
+
+ def test_ansible_node_len(self):
+ a = ansible_common.AnsibleNode()
+ len(a)
+
+ def test_ansible_node_repr(self):
+ a = ansible_common.AnsibleNode()
+ repr(a)
+
+ def test_ansible_node_iter(self):
+ a = ansible_common.AnsibleNode()
+ for _ in a:
+ pass
+
+ def test_is_role(self):
+ a = ansible_common.AnsibleNode()
+ self.assertFalse(a.is_role("", default="foo"))
+
+ def test_ansible_node_get_tuple(self):
+ a = ansible_common.AnsibleNode({"name": "name"})
+ self.assertEqual(a.get_tuple(), ('name', a))
+
+ def test_gen_inventory_line(self):
+ a = ansible_common.AnsibleNode(defaultdict(str))
+ self.assertEqual(a.gen_inventory_line(), "")
+
+ def test_ansible_node_delitem(self):
+ a = ansible_common.AnsibleNode({"name": "name"})
+ del a['name']
+
+ def test_ansible_node_getattr(self):
+ a = ansible_common.AnsibleNode({"name": "name"})
+ self.assertEqual(getattr(a, "nosuch", None), None)
+
+
+class AnsibleNodeDictTestCase(unittest.TestCase):
+ def test_ansible_node_dict(self):
+ n = ansible_common.AnsibleNode
+ ansible_common.AnsibleNodeDict(n, {})
+
+ def test_ansible_node_dict_len(self):
+ n = ansible_common.AnsibleNode
+ a = ansible_common.AnsibleNodeDict(n, {})
+ len(a)
+
+ def test_ansible_node_dict_repr(self):
+ n = ansible_common.AnsibleNode
+ a = ansible_common.AnsibleNodeDict(n, {})
+ repr(a)
+
+ def test_ansible_node_dict_iter(self):
+ n = ansible_common.AnsibleNode
+ a = ansible_common.AnsibleNodeDict(n, {})
+ for _ in a:
+ pass
+
+ def test_ansible_node_dict_get(self):
+ n = ansible_common.AnsibleNode
+ a = ansible_common.AnsibleNodeDict(n, {})
+ self.assertIsNone(a.get(""))
+
+ def test_gen_inventory_lines_for_all_of_type(self):
+ n = ansible_common.AnsibleNode
+ a = ansible_common.AnsibleNodeDict(n, {})
+ self.assertEqual(a.gen_inventory_lines_for_all_of_type(""), [])
+
+ def test_gen_inventory_lines(self):
+ n = ansible_common.AnsibleNode
+ a = ansible_common.AnsibleNodeDict(n, [{
+ "name": "name", "user": "user", "password": "PASS",
+ "role": "role",
+ }])
+ self.assertEqual(a.gen_all_inventory_lines(),
+ ["name ansible_ssh_pass=PASS ansible_user=user"])
+
+
+class AnsibleCommonTestCase(unittest.TestCase):
+ def test_get_timeouts(self):
+ self.assertAlmostEquals(ansible_common.AnsibleCommon.get_timeout(-100), 1200.0)
+
+ def test__init__(self):
+ ansible_common.AnsibleCommon({})
+
+ def test_reset(self):
+ a = ansible_common.AnsibleCommon({})
+ a.reset()
+
+ def test_do_install_no_dir(self):
+ a = ansible_common.AnsibleCommon({})
+ self.assertRaises(OSError, a.do_install, '', '')
+
+ def test_gen_inventory_dict(self):
+ nodes = [{
+ "name": "name", "user": "user", "password": "PASS",
+ "role": "role",
+ }]
+ a = ansible_common.AnsibleCommon(nodes)
+ a.gen_inventory_ini_dict()
+ self.assertEqual(a.inventory_dict, {
+ 'nodes': ['name ansible_ssh_pass=PASS ansible_user=user'],
+ 'role': ['name']
+ })
+
+ def test_deploy_dir(self):
+ a = ansible_common.AnsibleCommon({})
+ self.assertRaises(ValueError, getattr, a, "deploy_dir")
+
+ def test_deploy_dir_set(self):
+ a = ansible_common.AnsibleCommon({})
+ a.deploy_dir = ""
+
+ def test_deploy_dir_set_get(self):
+ a = ansible_common.AnsibleCommon({})
+ a.deploy_dir = "d"
+ self.assertEqual(a.deploy_dir, "d")
+
+ @mock.patch('{}.open'.format(PREFIX))
+ def test__gen_ansible_playbook_file_list(self, _):
+ d = tempfile.mkdtemp()
+ try:
+ a = ansible_common.AnsibleCommon({})
+ a._gen_ansible_playbook_file(["a"], d)
+ finally:
+ os.rmdir(d)
+
+ @mock.patch('{}.NamedTemporaryFile'.format(PREFIX))
+ @mock.patch('{}.open'.format(PREFIX))
+ def test__gen_ansible_inventory_file(self, _, __):
+ nodes = [{
+ "name": "name", "user": "user", "password": "PASS",
+ "role": "role",
+ }]
+ d = tempfile.mkdtemp()
+ try:
+ a = ansible_common.AnsibleCommon(nodes)
+ a.gen_inventory_ini_dict()
+ inv_context = a._gen_ansible_inventory_file(d)
+ with inv_context:
+ c = StringIO()
+ inv_context.write_func(c)
+ self.assertIn("ansible_ssh_pass=PASS", c.getvalue())
+ finally:
+ os.rmdir(d)
+
+ @mock.patch('{}.NamedTemporaryFile'.format(PREFIX))
+ @mock.patch('{}.open'.format(PREFIX))
+ def test__gen_ansible_playbook_file_list_multiple(self, _, __):
+ d = tempfile.mkdtemp()
+ try:
+ a = ansible_common.AnsibleCommon({})
+ a._gen_ansible_playbook_file(["a", "b"], d)
+ finally:
+ os.rmdir(d)
+
+ @mock.patch('{}.NamedTemporaryFile'.format(PREFIX))
+ @mock.patch('{}.Popen'.format(PREFIX))
+ @mock.patch('{}.open'.format(PREFIX))
+ def test_do_install_tmp_dir(self, _, mock_popen, __):
+ mock_popen.return_value.communicate.return_value = "", ""
+ mock_popen.return_value.wait.return_value = 0
+ d = tempfile.mkdtemp()
+ try:
+ a = ansible_common.AnsibleCommon({})
+ a.do_install('', d)
+ finally:
+ os.rmdir(d)
+
+ @mock.patch('{}.NamedTemporaryFile'.format(PREFIX))
+ @mock.patch('{}.Popen'.format(PREFIX))
+ @mock.patch('{}.open'.format(PREFIX))
+ def test_execute_ansible_check(self, _, mock_popen, __):
+ mock_popen.return_value.communicate.return_value = "", ""
+ mock_popen.return_value.wait.return_value = 0
+ d = tempfile.mkdtemp()
+ try:
+ a = ansible_common.AnsibleCommon({})
+ a.execute_ansible('', d, ansible_check=True, verbose=True)
+ finally:
+ os.rmdir(d)
diff --git a/yardstick/tests/unit/common/test_httpClient.py b/yardstick/tests/unit/common/test_httpClient.py
new file mode 100644
index 000000000..eb09d1a52
--- /dev/null
+++ b/yardstick/tests/unit/common/test_httpClient.py
@@ -0,0 +1,43 @@
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from __future__ import absolute_import
+
+import unittest
+
+import mock
+from oslo_serialization import jsonutils
+
+from yardstick.common import httpClient
+
+
+class HttpClientTestCase(unittest.TestCase):
+
+ @mock.patch('yardstick.common.httpClient.requests')
+ def test_post(self, mock_requests):
+ url = 'http://localhost:5000/hello'
+ data = {'hello': 'world'}
+ headers = {'Content-Type': 'application/json'}
+ httpClient.HttpClient().post(url, data)
+ mock_requests.post.assert_called_with(
+ url, data=jsonutils.dump_as_bytes(data),
+ headers=headers)
+
+ @mock.patch('yardstick.common.httpClient.requests')
+ def test_get(self, mock_requests):
+ url = 'http://localhost:5000/hello'
+ httpClient.HttpClient().get(url)
+ mock_requests.get.assert_called_with(url)
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/common/test_openstack_utils.py b/yardstick/tests/unit/common/test_openstack_utils.py
new file mode 100644
index 000000000..bf468489e
--- /dev/null
+++ b/yardstick/tests/unit/common/test_openstack_utils.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# Unittest for yardstick.common.openstack_utils
+
+from __future__ import absolute_import
+import unittest
+import mock
+
+from yardstick.common import openstack_utils
+
+
+class GetCredentialsTestCase(unittest.TestCase):
+
+ @mock.patch('yardstick.common.openstack_utils.os')
+ def test_get_credentials(self, _):
+ with mock.patch.dict('os.environ', {'OS_IDENTITY_API_VERSION': '2'},
+ clear=True):
+ openstack_utils.get_credentials()
+
+
+class GetHeatApiVersionTestCase(unittest.TestCase):
+
+ def test_get_heat_api_version_check_result(self):
+ API = 'HEAT_API_VERSION'
+ expected_result = '2'
+
+ with mock.patch.dict('os.environ', {API: '2'}, clear=True):
+ api_version = openstack_utils.get_heat_api_version()
+ self.assertEqual(api_version, expected_result)
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/common/test_process.py b/yardstick/tests/unit/common/test_process.py
new file mode 100644
index 000000000..1c6dfec27
--- /dev/null
+++ b/yardstick/tests/unit/common/test_process.py
@@ -0,0 +1,150 @@
+# Copyright (c) 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.
+
+import mock
+import unittest
+
+from oslo_utils import encodeutils
+
+from yardstick.common import exceptions
+from yardstick.common import process
+
+
+class ProcessTestcase(unittest.TestCase):
+ def test_check_if_procces_failed_None(self):
+ p = mock.MagicMock(**{"exitcode": None, "name": "debug"})
+ process.check_if_process_failed(p)
+
+ def test_check_if_procces_failed_0(self):
+ p = mock.MagicMock(**{"exitcode": 0, "name": "debug"})
+ process.check_if_process_failed(p)
+
+ def test_check_if_procces_failed_1(self):
+ p = mock.MagicMock(**{"exitcode": 1, "name": "debug"})
+ with self.assertRaises(RuntimeError):
+ process.check_if_process_failed(p)
+
+
+@mock.patch("yardstick.common.process.multiprocessing")
+class TerminateChildrenTestcase(unittest.TestCase):
+ def test_some_children(self, mock_multiprocessing):
+ p1 = mock.MagicMock()
+ p2 = mock.MagicMock()
+ mock_multiprocessing.active_children.return_value = [p1, p2]
+ process.terminate_children()
+
+ def test_no_children(self, mock_multiprocessing):
+ mock_multiprocessing.active_children.return_value = []
+ process.terminate_children()
+
+
+class ExecuteTestCase(unittest.TestCase):
+
+ RET_CODE_OK = 0
+ RET_CODE_WRONG = 1
+
+ def setUp(self):
+ self._mock_create_process = mock.patch.object(process,
+ 'create_process')
+ self.mock_create_process = self._mock_create_process.start()
+ self.obj = mock.Mock()
+ self.cmd = mock.Mock()
+ self.obj.communicate = mock.Mock()
+ self.stdout = 'std out'
+ self.stderr = 'std err'
+ self.obj.communicate.return_value = (self.stdout, self.stderr)
+ self.mock_create_process.return_value = (self.obj, self.cmd)
+ self.input_cmd = 'input cmd'
+ self.additional_env = mock.Mock()
+
+ def test_execute_with_input(self):
+ process_input = 'process input'
+ self.obj.returncode = self.RET_CODE_OK
+ out = process.execute(self.input_cmd, process_input=process_input,
+ additional_env=self.additional_env)
+ self.obj.communicate.assert_called_once_with(
+ encodeutils.to_utf8(process_input))
+ self.mock_create_process.assert_called_once_with(
+ self.input_cmd, run_as_root=False,
+ additional_env=self.additional_env)
+ self.assertEqual(self.stdout, out)
+
+ def test_execute_no_input(self):
+ self.obj.returncode = self.RET_CODE_OK
+ out = process.execute(self.input_cmd,
+ additional_env=self.additional_env)
+ self.obj.communicate.assert_called_once_with(None)
+ self.mock_create_process.assert_called_once_with(
+ self.input_cmd, run_as_root=False,
+ additional_env=self.additional_env)
+ self.assertEqual(self.stdout, out)
+
+ def test_execute_exception(self):
+ self.obj.returncode = self.RET_CODE_WRONG
+ self.assertRaises(exceptions.ProcessExecutionError, process.execute,
+ self.input_cmd, additional_env=self.additional_env)
+ self.obj.communicate.assert_called_once_with(None)
+
+ def test_execute_with_extra_code(self):
+ self.obj.returncode = self.RET_CODE_WRONG
+ out = process.execute(self.input_cmd,
+ additional_env=self.additional_env,
+ extra_ok_codes=[self.RET_CODE_WRONG])
+ self.obj.communicate.assert_called_once_with(None)
+ self.mock_create_process.assert_called_once_with(
+ self.input_cmd, run_as_root=False,
+ additional_env=self.additional_env)
+ self.assertEqual(self.stdout, out)
+
+ def test_execute_exception_no_check(self):
+ self.obj.returncode = self.RET_CODE_WRONG
+ out = process.execute(self.input_cmd,
+ additional_env=self.additional_env,
+ check_exit_code=False)
+ self.obj.communicate.assert_called_once_with(None)
+ self.mock_create_process.assert_called_once_with(
+ self.input_cmd, run_as_root=False,
+ additional_env=self.additional_env)
+ self.assertEqual(self.stdout, out)
+
+
+class CreateProcessTestCase(unittest.TestCase):
+
+ @mock.patch.object(process, 'subprocess_popen')
+ def test_process_string_command(self, mock_subprocess_popen):
+ cmd = 'command'
+ obj = mock.Mock()
+ mock_subprocess_popen.return_value = obj
+ out1, out2 = process.create_process(cmd)
+ self.assertEqual(obj, out1)
+ self.assertEqual([cmd], out2)
+
+ @mock.patch.object(process, 'subprocess_popen')
+ def test_process_list_command(self, mock_subprocess_popen):
+ cmd = ['command']
+ obj = mock.Mock()
+ mock_subprocess_popen.return_value = obj
+ out1, out2 = process.create_process(cmd)
+ self.assertEqual(obj, out1)
+ self.assertEqual(cmd, out2)
+
+ @mock.patch.object(process, 'subprocess_popen')
+ def test_process_with_env(self, mock_subprocess_popen):
+ cmd = ['command']
+ obj = mock.Mock()
+ additional_env = {'var1': 'value1'}
+ mock_subprocess_popen.return_value = obj
+ out1, out2 = process.create_process(cmd, additional_env=additional_env)
+ self.assertEqual(obj, out1)
+ self.assertEqual(['env', 'var1=value1'] + cmd, out2)
diff --git a/yardstick/tests/unit/common/test_template_format.py b/yardstick/tests/unit/common/test_template_format.py
new file mode 100644
index 000000000..44aa80333
--- /dev/null
+++ b/yardstick/tests/unit/common/test_template_format.py
@@ -0,0 +1,54 @@
+# 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: this file is copied from python-heatclient and slightly modified
+
+from __future__ import absolute_import
+import mock
+import unittest
+import yaml
+
+from yardstick.common import template_format
+
+
+class TemplateFormatTestCase(unittest.TestCase):
+
+ def test_parse_to_value_exception(self):
+
+ # TODO(elfoley): Don't hide the error that occurs in
+ # template_format.parse
+ # TODO(elfoley): Separate these tests; one per error type
+ with mock.patch.object(yaml, 'load') as yaml_loader:
+ yaml_loader.side_effect = yaml.scanner.ScannerError()
+ self.assertRaises(ValueError, template_format.parse, 'FOOBAR')
+ yaml_loader.side_effect = yaml.parser.ParserError()
+ self.assertRaises(ValueError, template_format.parse, 'FOOBAR')
+ yaml_loader.side_effect = \
+ yaml.reader.ReaderError('', '', '', '', '')
+ self.assertRaises(ValueError, template_format.parse, 'FOOBAR')
+
+ def test_parse_no_version_format(self):
+
+ yaml = ''
+ self.assertRaises(ValueError, template_format.parse, yaml)
+ yaml2 = "Parameters: {}\n" \
+ "Mappings: {}\n" \
+ "Resources: {}\n" \
+ "Outputs: {}"
+ self.assertRaises(ValueError, template_format.parse, yaml2)
+
+
+def main():
+ unittest.main()
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/common/test_utils.py b/yardstick/tests/unit/common/test_utils.py
new file mode 100644
index 000000000..452b93a56
--- /dev/null
+++ b/yardstick/tests/unit/common/test_utils.py
@@ -0,0 +1,1085 @@
+##############################################################################
+# Copyright (c) 2015 Ericsson AB 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
+##############################################################################
+
+# Unittest for yardstick.common.utils
+
+from __future__ import absolute_import
+
+from copy import deepcopy
+import errno
+import ipaddress
+from itertools import product, chain
+import mock
+import os
+import six
+from six.moves import configparser
+import unittest
+
+import yardstick
+from yardstick.common import utils
+from yardstick.common import constants
+
+
+class IterSubclassesTestCase(unittest.TestCase):
+ # Disclaimer: this class is a modified copy from
+ # rally/tests/unit/common/plugin/test_discover.py
+ # Copyright 2015: Mirantis Inc.
+
+ def test_itersubclasses(self):
+ class A(object):
+ pass
+
+ class B(A):
+ pass
+
+ class C(A):
+ pass
+
+ class D(C):
+ pass
+
+ self.assertEqual([B, C, D], list(utils.itersubclasses(A)))
+
+
+class ImportModulesFromPackageTestCase(unittest.TestCase):
+
+ @mock.patch('yardstick.common.utils.os.walk')
+ def test_import_modules_from_package_no_mod(self, mock_walk):
+ yardstick_root = os.path.dirname(os.path.dirname(yardstick.__file__))
+ mock_walk.return_value = ([
+ (os.path.join(yardstick_root, 'foo'), ['bar'], ['__init__.py']),
+ (os.path.join(yardstick_root, 'foo', 'bar'), [], ['baz.txt', 'qux.rst'])
+ ])
+
+ utils.import_modules_from_package('foo.bar')
+
+ @mock.patch('yardstick.common.utils.os.walk')
+ @mock.patch('yardstick.common.utils.importutils')
+ def test_import_modules_from_package(self, mock_importutils, mock_walk):
+
+ yardstick_root = os.path.dirname(os.path.dirname(yardstick.__file__))
+ mock_walk.return_value = ([
+ (os.path.join(yardstick_root, 'foo', os.pardir, 'bar'), [], ['baz.py'])
+ ])
+
+ utils.import_modules_from_package('foo.bar')
+ mock_importutils.import_module.assert_called_with('bar.baz')
+
+
+class GetParaFromYaml(unittest.TestCase):
+
+ @mock.patch('yardstick.common.utils.os.environ.get')
+ def test_get_param_para_not_found(self, get_env):
+ file_path = 'config_sample.yaml'
+ get_env.return_value = self._get_file_abspath(file_path)
+ args = 'releng.file'
+ default = 'hello'
+ self.assertTrue(constants.get_param(args, default), default)
+
+ @mock.patch('yardstick.common.utils.os.environ.get')
+ def test_get_param_para_exists(self, get_env):
+ file_path = 'config_sample.yaml'
+ get_env.return_value = self._get_file_abspath(file_path)
+ args = 'releng.dir'
+ para = '/home/opnfv/repos/releng'
+ self.assertEqual(para, constants.get_param(args))
+
+ def _get_file_abspath(self, filename):
+ curr_path = os.path.dirname(os.path.abspath(__file__))
+ file_path = os.path.join(curr_path, filename)
+ return file_path
+
+
+class CommonUtilTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.data = {
+ "benchmark": {
+ "data": {
+ "mpstat": {
+ "cpu0": {
+ "%sys": "0.00",
+ "%idle": "99.00"
+ },
+ "loadavg": [
+ "1.09",
+ "0.29"
+ ]
+ },
+ "rtt": "1.03"
+ }
+ }
+ }
+
+ def test__dict_key_flatten(self):
+ line = 'mpstat.loadavg1=0.29,rtt=1.03,mpstat.loadavg0=1.09,' \
+ 'mpstat.cpu0.%idle=99.00,mpstat.cpu0.%sys=0.00'
+ # need to sort for assert to work
+ line = ",".join(sorted(line.split(',')))
+ flattened_data = utils.flatten_dict_key(
+ self.data['benchmark']['data'])
+ result = ",".join(
+ ("=".join(item) for item in sorted(flattened_data.items())))
+ self.assertEqual(result, line)
+
+
+class TestMacAddressToHex(unittest.TestCase):
+
+ def test_mac_address_to_hex_list(self):
+ self.assertEqual(utils.mac_address_to_hex_list("ea:3e:e1:9a:99:e8"),
+ ['0xea', '0x3e', '0xe1', '0x9a', '0x99', '0xe8'])
+
+
+class TranslateToStrTestCase(unittest.TestCase):
+
+ def test_translate_to_str_unicode(self):
+ input_str = u'hello'
+ output_str = utils.translate_to_str(input_str)
+
+ result = 'hello'
+ self.assertEqual(result, output_str)
+
+ def test_translate_to_str_dict_list_unicode(self):
+ input_str = {
+ u'hello': {u'hello': [u'world']}
+ }
+ output_str = utils.translate_to_str(input_str)
+
+ result = {
+ 'hello': {'hello': ['world']}
+ }
+ self.assertEqual(result, output_str)
+
+ def test_translate_to_str_non_string(self):
+ input_value = object()
+ result = utils.translate_to_str(input_value)
+ self.assertIs(input_value, result)
+
+
+class TestParseCpuInfo(unittest.TestCase):
+
+ def test_single_socket_no_hyperthread(self):
+ cpuinfo = """\
+processor : 2
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel Core Processor (Haswell, no TSX)
+stepping : 1
+microcode : 0x1
+cpu MHz : 2294.684
+cache size : 4096 KB
+physical id : 0
+siblings : 5
+core id : 2
+cpu cores : 5
+apicid : 2
+initial apicid : 2
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat
+bugs :
+bogomips : 4589.36
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 3
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel Core Processor (Haswell, no TSX)
+stepping : 1
+microcode : 0x1
+cpu MHz : 2294.684
+cache size : 4096 KB
+physical id : 0
+siblings : 5
+core id : 3
+cpu cores : 5
+apicid : 3
+initial apicid : 3
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat
+bugs :
+bogomips : 4589.36
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 4
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel Core Processor (Haswell, no TSX)
+stepping : 1
+microcode : 0x1
+cpu MHz : 2294.684
+cache size : 4096 KB
+physical id : 0
+siblings : 5
+core id : 4
+cpu cores : 5
+apicid : 4
+initial apicid : 4
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat
+bugs :
+bogomips : 4589.36
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+"""
+ socket_map = utils.SocketTopology.parse_cpuinfo(cpuinfo)
+ assert sorted(socket_map.keys()) == [0]
+ assert sorted(socket_map[0].keys()) == [2, 3, 4]
+
+ def test_single_socket_hyperthread(self):
+ cpuinfo = """\
+processor : 5
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Xeon(R) CPU E3-1275 v3 @ 3.50GHz
+stepping : 3
+microcode : 0x1d
+cpu MHz : 3501.708
+cache size : 8192 KB
+physical id : 0
+siblings : 8
+core id : 1
+cpu cores : 4
+apicid : 3
+initial apicid : 3
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 6987.36
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 6
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Xeon(R) CPU E3-1275 v3 @ 3.50GHz
+stepping : 3
+microcode : 0x1d
+cpu MHz : 3531.829
+cache size : 8192 KB
+physical id : 0
+siblings : 8
+core id : 2
+cpu cores : 4
+apicid : 5
+initial apicid : 5
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 6987.36
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 7
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Xeon(R) CPU E3-1275 v3 @ 3.50GHz
+stepping : 3
+microcode : 0x1d
+cpu MHz : 3500.213
+cache size : 8192 KB
+physical id : 0
+siblings : 8
+core id : 3
+cpu cores : 4
+apicid : 7
+initial apicid : 7
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 6987.24
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+"""
+ socket_map = utils.SocketTopology.parse_cpuinfo(cpuinfo)
+ assert sorted(socket_map.keys()) == [0]
+ assert sorted(socket_map[0].keys()) == [1, 2, 3]
+ assert sorted(socket_map[0][1]) == [5]
+ assert sorted(socket_map[0][2]) == [6]
+ assert sorted(socket_map[0][3]) == [7]
+
+ def test_dual_socket_hyperthread(self):
+ cpuinfo = """\
+processor : 1
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.976
+cache size : 56320 KB
+physical id : 0
+siblings : 44
+core id : 1
+cpu cores : 22
+apicid : 2
+initial apicid : 2
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4401.07
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 2
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1226.892
+cache size : 56320 KB
+physical id : 0
+siblings : 44
+core id : 2
+cpu cores : 22
+apicid : 4
+initial apicid : 4
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4400.84
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 43
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.305
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 28
+cpu cores : 22
+apicid : 120
+initial apicid : 120
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4411.31
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 44
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.305
+cache size : 56320 KB
+physical id : 0
+siblings : 44
+core id : 0
+cpu cores : 22
+apicid : 1
+initial apicid : 1
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4410.61
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 85
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.573
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 26
+cpu cores : 22
+apicid : 117
+initial apicid : 117
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4409.07
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 86
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.305
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 27
+cpu cores : 22
+apicid : 119
+initial apicid : 119
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4406.62
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 87
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.708
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 28
+cpu cores : 22
+apicid : 121
+initial apicid : 121
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4413.48
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+"""
+ socket_map = utils.SocketTopology.parse_cpuinfo(cpuinfo)
+ assert sorted(socket_map.keys()) == [0, 1]
+ assert sorted(socket_map[0].keys()) == [0, 1, 2]
+ assert sorted(socket_map[1].keys()) == [26, 27, 28]
+ assert sorted(socket_map[0][0]) == [44]
+ assert sorted(socket_map[0][1]) == [1]
+ assert sorted(socket_map[0][2]) == [2]
+ assert sorted(socket_map[1][26]) == [85]
+ assert sorted(socket_map[1][27]) == [86]
+ assert sorted(socket_map[1][28]) == [43, 87]
+
+ def test_dual_socket_no_hyperthread(self):
+ cpuinfo = """\
+processor : 1
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.976
+cache size : 56320 KB
+physical id : 0
+siblings : 44
+core id : 1
+cpu cores : 22
+apicid : 2
+initial apicid : 2
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4401.07
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 2
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1226.892
+cache size : 56320 KB
+physical id : 0
+siblings : 44
+core id : 2
+cpu cores : 22
+apicid : 4
+initial apicid : 4
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4400.84
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 43
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.305
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 28
+cpu cores : 22
+apicid : 120
+initial apicid : 120
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4411.31
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 44
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.305
+cache size : 56320 KB
+physical id : 0
+siblings : 44
+core id : 0
+cpu cores : 22
+apicid : 1
+initial apicid : 1
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4410.61
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 85
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.573
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 26
+cpu cores : 22
+apicid : 117
+initial apicid : 117
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4409.07
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 86
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.305
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 27
+cpu cores : 22
+apicid : 119
+initial apicid : 119
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4406.62
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+processor : 87
+vendor_id : GenuineIntel
+cpu family : 6
+model : 79
+model name : Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20GHz
+stepping : 1
+microcode : 0xb00001f
+cpu MHz : 1200.708
+cache size : 56320 KB
+physical id : 1
+siblings : 44
+core id : 28
+cpu cores : 22
+apicid : 121
+initial apicid : 121
+fpu : yes
+fpu_exception : yes
+cpuid level : 20
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 intel_ppin intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts
+bugs :
+bogomips : 4413.48
+clflush size : 64
+cache_alignment : 64
+address sizes : 46 bits physical, 48 bits virtual
+power management:
+
+"""
+ socket_map = utils.SocketTopology.parse_cpuinfo(cpuinfo)
+ processors = socket_map.processors()
+ assert processors == [1, 2, 43, 44, 85, 86, 87]
+ cores = socket_map.cores()
+ assert cores == [0, 1, 2, 26, 27, 28]
+ sockets = socket_map.sockets()
+ assert sockets == [0, 1]
+
+
+class ChangeObjToDictTestCase(unittest.TestCase):
+
+ def test_change_obj_to_dict(self):
+ class A(object):
+ def __init__(self):
+ self.name = 'yardstick'
+
+ obj = A()
+ obj_r = utils.change_obj_to_dict(obj)
+ obj_s = {'name': 'yardstick'}
+ self.assertEqual(obj_r, obj_s)
+
+
+class SetDictValueTestCase(unittest.TestCase):
+
+ def test_set_dict_value(self):
+ input_dic = {
+ 'hello': 'world'
+ }
+ output_dic = utils.set_dict_value(input_dic, 'welcome.to', 'yardstick')
+ self.assertEqual(output_dic.get('welcome', {}).get('to'), 'yardstick')
+
+
+class RemoveFileTestCase(unittest.TestCase):
+
+ def test_remove_file(self):
+ try:
+ utils.remove_file('notexistfile.txt')
+ except Exception as e: # pylint: disable=broad-except
+ # NOTE(ralonsoh): to narrow the scope of this exception.
+ self.assertTrue(isinstance(e, OSError))
+
+
+class TestUtils(unittest.TestCase):
+
+ @mock.patch('yardstick.common.utils.os.makedirs')
+ def test_makedirs(self, *_):
+ self.assertIsNone(utils.makedirs('a/b/c/d'))
+
+ @mock.patch('yardstick.common.utils.os.makedirs')
+ def test_makedirs_exists(self, mock_os_makedirs):
+ mock_os_makedirs.side_effect = OSError(errno.EEXIST, 'exists')
+ self.assertIsNone(utils.makedirs('a/b/c/d'))
+
+ @mock.patch('yardstick.common.utils.os.makedirs')
+ def test_makedirs_busy(self, mock_os_makedirs):
+ mock_os_makedirs.side_effect = OSError(errno.EBUSY, 'busy')
+ with self.assertRaises(OSError):
+ utils.makedirs('a/b/c/d')
+
+ @mock.patch('yardstick.common.utils.jsonify')
+ def test_result_handler(self, mock_jsonify):
+ mock_jsonify.return_value = 432
+
+ self.assertEqual(utils.result_handler('x', 234), 432)
+ mock_jsonify.assert_called_once_with({'status': 'x', 'result': 234})
+
+ @mock.patch('random.randint')
+ @mock.patch('socket.socket')
+ def test_get_free_port(self, mock_socket, mock_randint):
+ mock_randint.return_value = 7777
+ s = mock_socket('x', 'y')
+ s.connect_ex.side_effect = iter([0, 1])
+ result = utils.get_free_port('10.20.30.40')
+ self.assertEqual(result, 7777)
+ self.assertEqual(s.connect_ex.call_count, 2)
+
+ @mock.patch('subprocess.check_output')
+ def test_execute_command(self, mock_check_output):
+ expected = ['hello world', '1234']
+ mock_check_output.return_value = os.linesep.join(expected)
+ result = utils.execute_command('my_command arg1 arg2')
+ self.assertEqual(result, expected)
+
+ @mock.patch('subprocess.Popen')
+ def test_source_env(self, mock_popen):
+ base_env = deepcopy(os.environ)
+ mock_process = mock_popen()
+ output_list = [
+ 'garbage line before',
+ 'NEW_ENV_VALUE=234',
+ 'garbage line after',
+ ]
+ mock_process.communicate.return_value = os.linesep.join(output_list), '', 0
+ expected = {'NEW_ENV_VALUE': '234'}
+ result = utils.source_env('my_file')
+ self.assertDictEqual(result, expected)
+ os.environ.clear()
+ os.environ.update(base_env)
+
+ @mock.patch('yardstick.common.utils.configparser.ConfigParser')
+ def test_parse_ini_file(self, mock_config_parser_type):
+ defaults = {
+ 'default1': 'value1',
+ 'default2': 'value2',
+ }
+ s1 = {
+ 'key1': 'value11',
+ 'key2': 'value22',
+ }
+ s2 = {
+ 'key1': 'value123',
+ 'key2': 'value234',
+ }
+
+ mock_config_parser = mock_config_parser_type()
+ mock_config_parser.read.return_value = True
+ mock_config_parser.sections.return_value = ['s1', 's2']
+ mock_config_parser.items.side_effect = iter([
+ defaults.items(),
+ s1.items(),
+ s2.items(),
+ ])
+
+ expected = {
+ 'DEFAULT': defaults,
+ 's1': s1,
+ 's2': s2,
+ }
+ result = utils.parse_ini_file('my_path')
+ self.assertDictEqual(result, expected)
+
+ @mock.patch('yardstick.common.utils.configparser.ConfigParser')
+ def test_parse_ini_file_missing_section_header(self, mock_config_parser_type):
+ mock_config_parser = mock_config_parser_type()
+ mock_config_parser.read.side_effect = \
+ configparser.MissingSectionHeaderError(mock.Mock(), 321, mock.Mock())
+
+ with self.assertRaises(configparser.MissingSectionHeaderError):
+ utils.parse_ini_file('my_path')
+
+ @mock.patch('yardstick.common.utils.configparser.ConfigParser')
+ def test_parse_ini_file_no_file(self, mock_config_parser_type):
+ mock_config_parser = mock_config_parser_type()
+ mock_config_parser.read.return_value = False
+ with self.assertRaises(RuntimeError):
+ utils.parse_ini_file('my_path')
+
+ @mock.patch('yardstick.common.utils.configparser.ConfigParser')
+ def test_parse_ini_file_no_default_section_header(self, mock_config_parser_type):
+ s1 = {
+ 'key1': 'value11',
+ 'key2': 'value22',
+ }
+ s2 = {
+ 'key1': 'value123',
+ 'key2': 'value234',
+ }
+
+ mock_config_parser = mock_config_parser_type()
+ mock_config_parser.read.return_value = True
+ mock_config_parser.sections.return_value = ['s1', 's2']
+ mock_config_parser.items.side_effect = iter([
+ configparser.NoSectionError(mock.Mock()),
+ s1.items(),
+ s2.items(),
+ ])
+
+ expected = {
+ 'DEFAULT': {},
+ 's1': s1,
+ 's2': s2,
+ }
+ result = utils.parse_ini_file('my_path')
+ self.assertDictEqual(result, expected)
+
+ def test_join_non_strings(self):
+ self.assertEqual(utils.join_non_strings(':'), '')
+ self.assertEqual(utils.join_non_strings(':', 'a'), 'a')
+ self.assertEqual(utils.join_non_strings(':', 'a', 2, 'c'), 'a:2:c')
+ self.assertEqual(utils.join_non_strings(':', ['a', 2, 'c']), 'a:2:c')
+ self.assertEqual(utils.join_non_strings(':', 'abc'), 'abc')
+
+ def test_validate_non_string_sequence(self):
+ self.assertEqual(utils.validate_non_string_sequence([1, 2, 3]), [1, 2, 3])
+ self.assertIsNone(utils.validate_non_string_sequence('123'))
+ self.assertIsNone(utils.validate_non_string_sequence(1))
+
+ self.assertEqual(utils.validate_non_string_sequence(1, 2), 2)
+ self.assertEqual(utils.validate_non_string_sequence(1, default=2), 2)
+
+ with self.assertRaises(RuntimeError):
+ utils.validate_non_string_sequence(1, raise_exc=RuntimeError)
+
+ def test_error_class(self):
+ with self.assertRaises(RuntimeError):
+ utils.ErrorClass()
+
+ error_instance = utils.ErrorClass(test='')
+ with self.assertRaises(AttributeError):
+ error_instance.get_name()
+
+
+class TestUtilsIpAddrMethods(unittest.TestCase):
+
+ GOOD_IP_V4_ADDRESS_STR_LIST = [
+ u'0.0.0.0',
+ u'10.20.30.40',
+ u'127.0.0.1',
+ u'10.20.30.40',
+ u'172.29.50.75',
+ u'192.168.230.9',
+ u'255.255.255.255',
+ ]
+
+ GOOD_IP_V4_MASK_STR_LIST = [
+ u'/1',
+ u'/8',
+ u'/13',
+ u'/19',
+ u'/24',
+ u'/32',
+ ]
+
+ GOOD_IP_V6_ADDRESS_STR_LIST = [
+ u'::1',
+ u'fe80::250:56ff:fe89:91ff',
+ u'123:4567:89ab:cdef:123:4567:89ab:cdef',
+ ]
+
+ GOOD_IP_V6_MASK_STR_LIST = [
+ u'/1',
+ u'/16',
+ u'/29',
+ u'/64',
+ u'/99',
+ u'/128',
+ ]
+
+ INVALID_IP_ADDRESS_STR_LIST = [
+ 1,
+ u'w.x.y.z',
+ u'10.20.30.40/33',
+ u'123:4567:89ab:cdef:123:4567:89ab:cdef/129',
+ ]
+
+ def test_safe_ip_address(self):
+ addr_list = self.GOOD_IP_V4_ADDRESS_STR_LIST
+ for addr in addr_list:
+ # test with no mask
+ expected = ipaddress.ip_address(addr)
+ self.assertEqual(utils.safe_ip_address(addr), expected, addr)
+
+ def test_safe_ip_address_v6_ip(self):
+ addr_list = self.GOOD_IP_V6_ADDRESS_STR_LIST
+ for addr in addr_list:
+ # test with no mask
+ expected = ipaddress.ip_address(addr)
+ self.assertEqual(utils.safe_ip_address(addr), expected, addr)
+
+ @mock.patch("yardstick.common.utils.logging")
+ def test_safe_ip_address_negative(self, *args):
+ # NOTE(ralonsoh): check the calls to mocked functions.
+ for value in self.INVALID_IP_ADDRESS_STR_LIST:
+ self.assertIsNone(utils.safe_ip_address(value), value)
+
+ addr_list = self.GOOD_IP_V4_ADDRESS_STR_LIST
+ mask_list = self.GOOD_IP_V4_MASK_STR_LIST
+ for addr_mask_pair in product(addr_list, mask_list):
+ value = ''.join(addr_mask_pair)
+ self.assertIsNone(utils.safe_ip_address(value), value)
+
+ addr_list = self.GOOD_IP_V6_ADDRESS_STR_LIST
+ mask_list = self.GOOD_IP_V6_MASK_STR_LIST
+ for addr_mask_pair in product(addr_list, mask_list):
+ value = ''.join(addr_mask_pair)
+ self.assertIsNone(utils.safe_ip_address(value), value)
+
+ def test_get_ip_version(self):
+ addr_list = self.GOOD_IP_V4_ADDRESS_STR_LIST
+ for addr in addr_list:
+ # test with no mask
+ self.assertEqual(utils.get_ip_version(addr), 4, addr)
+
+ def test_get_ip_version_v6_ip(self):
+ addr_list = self.GOOD_IP_V6_ADDRESS_STR_LIST
+ for addr in addr_list:
+ # test with no mask
+ self.assertEqual(utils.get_ip_version(addr), 6, addr)
+
+ @mock.patch("yardstick.common.utils.logging")
+ def test_get_ip_version_negative(self, *args):
+ # NOTE(ralonsoh): check the calls to mocked functions.
+ for value in self.INVALID_IP_ADDRESS_STR_LIST:
+ self.assertIsNone(utils.get_ip_version(value), value)
+
+ addr_list = self.GOOD_IP_V4_ADDRESS_STR_LIST
+ mask_list = self.GOOD_IP_V4_MASK_STR_LIST
+ for addr_mask_pair in product(addr_list, mask_list):
+ value = ''.join(addr_mask_pair)
+ self.assertIsNone(utils.get_ip_version(value), value)
+
+ addr_list = self.GOOD_IP_V6_ADDRESS_STR_LIST
+ mask_list = self.GOOD_IP_V6_MASK_STR_LIST
+ for addr_mask_pair in product(addr_list, mask_list):
+ value = ''.join(addr_mask_pair)
+ self.assertIsNone(utils.get_ip_version(value), value)
+
+ def test_ip_to_hex(self):
+ self.assertEqual(utils.ip_to_hex('0.0.0.0'), '00000000')
+ self.assertEqual(utils.ip_to_hex('10.20.30.40'), '0a141e28')
+ self.assertEqual(utils.ip_to_hex('127.0.0.1'), '7f000001')
+ self.assertEqual(utils.ip_to_hex('172.31.90.100'), 'ac1f5a64')
+ self.assertEqual(utils.ip_to_hex('192.168.254.253'), 'c0a8fefd')
+ self.assertEqual(utils.ip_to_hex('255.255.255.255'), 'ffffffff')
+
+ def test_ip_to_hex_v6_ip(self):
+ for value in self.GOOD_IP_V6_ADDRESS_STR_LIST:
+ self.assertEqual(utils.ip_to_hex(value), value)
+
+ @mock.patch("yardstick.common.utils.logging")
+ def test_ip_to_hex_negative(self, *args):
+ # NOTE(ralonsoh): check the calls to mocked functions.
+ addr_list = self.GOOD_IP_V4_ADDRESS_STR_LIST
+ mask_list = self.GOOD_IP_V4_MASK_STR_LIST
+ value_iter = (''.join(pair) for pair in product(addr_list, mask_list))
+ for value in chain(value_iter, self.INVALID_IP_ADDRESS_STR_LIST):
+ self.assertEqual(utils.ip_to_hex(value), value)
+
+
+class SafeDecodeUtf8TestCase(unittest.TestCase):
+
+ @unittest.skipIf(six.PY2,
+ 'This test should only be launched with Python 3.x')
+ def test_safe_decode_utf8(self):
+ _bytes = b'this is a byte array'
+ out = utils.safe_decode_utf8(_bytes)
+ self.assertIs(type(out), str)
+ self.assertEqual('this is a byte array', out)
+
+
+def main():
+ unittest.main()
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/common/test_yaml_loader.py b/yardstick/tests/unit/common/test_yaml_loader.py
new file mode 100644
index 000000000..90cbb8157
--- /dev/null
+++ b/yardstick/tests/unit/common/test_yaml_loader.py
@@ -0,0 +1,32 @@
+# 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: this file is copied from python-heatclient and slightly modified
+
+from __future__ import absolute_import
+import unittest
+
+from yardstick.common import yaml_loader
+
+
+class TemplateFormatTestCase(unittest.TestCase):
+
+ def test_parse_to_value_exception(self):
+
+ self.assertEquals(yaml_loader.yaml_load("string"), u"string")
+
+
+def main():
+ unittest.main()
+
+if __name__ == '__main__':
+ main()
diff --git a/yardstick/tests/unit/test_ssh.py b/yardstick/tests/unit/test_ssh.py
new file mode 100644
index 000000000..dbaae8c37
--- /dev/null
+++ b/yardstick/tests/unit/test_ssh.py
@@ -0,0 +1,572 @@
+# Copyright 2013: 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 file is a modified copy of
+# rally/tests/unit/common/test_sshutils.py
+
+from __future__ import absolute_import
+import os
+import socket
+import unittest
+from io import StringIO
+
+import mock
+from oslo_utils import encodeutils
+
+from yardstick import ssh
+from yardstick.ssh import SSHError
+from yardstick.ssh import SSH
+from yardstick.ssh import AutoConnectSSH
+
+
+class FakeParamikoException(Exception):
+ pass
+
+
+class SSHTestCase(unittest.TestCase):
+ """Test all small SSH methods."""
+
+ def setUp(self):
+ super(SSHTestCase, self).setUp()
+ self.test_client = ssh.SSH("root", "example.net")
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ def test_construct(self, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "pkey"
+ test_ssh = ssh.SSH("root", "example.net", port=33, pkey="key",
+ key_filename="kf", password="secret")
+ mock_ssh__get_pkey.assert_called_once_with("key")
+ self.assertEqual("root", test_ssh.user)
+ self.assertEqual("example.net", test_ssh.host)
+ self.assertEqual(33, test_ssh.port)
+ self.assertEqual("pkey", test_ssh.pkey)
+ self.assertEqual("kf", test_ssh.key_filename)
+ self.assertEqual("secret", test_ssh.password)
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ def test_ssh_from_node(self, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "pkey"
+ node = {
+ "user": "root", "ip": "example.net", "ssh_port": 33,
+ "key_filename": "kf", "password": "secret"
+ }
+ test_ssh = ssh.SSH.from_node(node)
+ self.assertEqual("root", test_ssh.user)
+ self.assertEqual("example.net", test_ssh.host)
+ self.assertEqual(33, test_ssh.port)
+ self.assertEqual("kf", test_ssh.key_filename)
+ self.assertEqual("secret", test_ssh.password)
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ def test_ssh_from_node_password_default(self, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "pkey"
+ node = {
+ "user": "root", "ip": "example.net", "ssh_port": 33,
+ "key_filename": "kf"
+ }
+ test_ssh = ssh.SSH.from_node(node)
+ self.assertEqual("root", test_ssh.user)
+ self.assertEqual("example.net", test_ssh.host)
+ self.assertEqual(33, test_ssh.port)
+ self.assertEqual("kf", test_ssh.key_filename)
+ self.assertIsNone(test_ssh.password)
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ def test_ssh_from_node_ssh_port_default(self, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "pkey"
+ node = {
+ "user": "root", "ip": "example.net",
+ "key_filename": "kf", "password": "secret"
+ }
+ test_ssh = ssh.SSH.from_node(node)
+ self.assertEqual("root", test_ssh.user)
+ self.assertEqual("example.net", test_ssh.host)
+ self.assertEqual(ssh.SSH.SSH_PORT, test_ssh.port)
+ self.assertEqual("kf", test_ssh.key_filename)
+ self.assertEqual("secret", test_ssh.password)
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ def test_ssh_from_node_key_filename_default(self, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "pkey"
+ node = {
+ "user": "root", "ip": "example.net", "ssh_port": 33,
+ "password": "secret"
+ }
+ test_ssh = ssh.SSH.from_node(node)
+ self.assertEqual("root", test_ssh.user)
+ self.assertEqual("example.net", test_ssh.host)
+ self.assertEqual(33, test_ssh.port)
+ self.assertIsNone(test_ssh.key_filename)
+ self.assertEqual("secret", test_ssh.password)
+
+ def test_construct_default(self):
+ self.assertEqual("root", self.test_client.user)
+ self.assertEqual("example.net", self.test_client.host)
+ self.assertEqual(22, self.test_client.port)
+ self.assertIsNone(self.test_client.pkey)
+ self.assertIsNone(self.test_client.key_filename)
+ self.assertIsNone(self.test_client.password)
+
+ @mock.patch("yardstick.ssh.paramiko")
+ def test__get_pkey_invalid(self, mock_paramiko):
+ mock_paramiko.SSHException = FakeParamikoException
+ rsa = mock_paramiko.rsakey.RSAKey
+ dss = mock_paramiko.dsskey.DSSKey
+ rsa.from_private_key.side_effect = mock_paramiko.SSHException
+ dss.from_private_key.side_effect = mock_paramiko.SSHException
+ self.assertRaises(ssh.SSHError, self.test_client._get_pkey, "key")
+
+ @mock.patch("yardstick.ssh.six.moves.StringIO")
+ @mock.patch("yardstick.ssh.paramiko")
+ def test__get_pkey_dss(self, mock_paramiko, mock_string_io):
+ mock_paramiko.SSHException = FakeParamikoException
+ mock_string_io.return_value = "string_key"
+ mock_paramiko.dsskey.DSSKey.from_private_key.return_value = "dss_key"
+ rsa = mock_paramiko.rsakey.RSAKey
+ rsa.from_private_key.side_effect = mock_paramiko.SSHException
+ key = self.test_client._get_pkey("key")
+ dss_calls = mock_paramiko.dsskey.DSSKey.from_private_key.mock_calls
+ self.assertEqual([mock.call("string_key")], dss_calls)
+ self.assertEqual(key, "dss_key")
+ mock_string_io.assert_called_once_with("key")
+
+ @mock.patch("yardstick.ssh.six.moves.StringIO")
+ @mock.patch("yardstick.ssh.paramiko")
+ def test__get_pkey_rsa(self, mock_paramiko, mock_string_io):
+ mock_paramiko.SSHException = FakeParamikoException
+ mock_string_io.return_value = "string_key"
+ mock_paramiko.rsakey.RSAKey.from_private_key.return_value = "rsa_key"
+ dss = mock_paramiko.dsskey.DSSKey
+ dss.from_private_key.side_effect = mock_paramiko.SSHException
+ key = self.test_client._get_pkey("key")
+ rsa_calls = mock_paramiko.rsakey.RSAKey.from_private_key.mock_calls
+ self.assertEqual([mock.call("string_key")], rsa_calls)
+ self.assertEqual(key, "rsa_key")
+ mock_string_io.assert_called_once_with("key")
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ @mock.patch("yardstick.ssh.paramiko")
+ def test__get_client(self, mock_paramiko, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "key"
+ fake_client = mock.Mock()
+ mock_paramiko.SSHClient.return_value = fake_client
+ mock_paramiko.AutoAddPolicy.return_value = "autoadd"
+
+ test_ssh = ssh.SSH("admin", "example.net", pkey="key")
+ client = test_ssh._get_client()
+
+ self.assertEqual(fake_client, client)
+ client_calls = [
+ mock.call.set_missing_host_key_policy("autoadd"),
+ mock.call.connect("example.net", username="admin",
+ port=22, pkey="key", key_filename=None,
+ password=None,
+ allow_agent=False, look_for_keys=False,
+ timeout=1),
+ ]
+ self.assertEqual(client_calls, client.mock_calls)
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ @mock.patch("yardstick.ssh.paramiko")
+ def test__get_client_with_exception(self, mock_paramiko, mock_ssh__get_pkey):
+ class MyError(Exception):
+ pass
+
+ mock_ssh__get_pkey.return_value = "pkey"
+ fake_client = mock.Mock()
+ fake_client.connect.side_effect = MyError
+ fake_client.set_missing_host_key_policy.return_value = None
+ mock_paramiko.SSHClient.return_value = fake_client
+ mock_paramiko.AutoAddPolicy.return_value = "autoadd"
+
+ test_ssh = ssh.SSH("admin", "example.net", pkey="key")
+
+ with self.assertRaises(SSHError) as raised:
+ test_ssh._get_client()
+
+ self.assertEqual(mock_paramiko.SSHClient.call_count, 1)
+ self.assertEqual(mock_paramiko.AutoAddPolicy.call_count, 1)
+ self.assertEqual(fake_client.set_missing_host_key_policy.call_count, 1)
+ self.assertEqual(fake_client.connect.call_count, 1)
+ exc_str = str(raised.exception)
+ self.assertIn('raised during connect', exc_str)
+ self.assertIn('MyError', exc_str)
+
+ @mock.patch("yardstick.ssh.SSH._get_pkey")
+ @mock.patch("yardstick.ssh.paramiko")
+ def test_copy(self, mock_paramiko, mock_ssh__get_pkey):
+ mock_ssh__get_pkey.return_value = "pkey"
+ fake_client = mock.Mock()
+ fake_client.connect.side_effect = IOError
+ mock_paramiko.SSHClient.return_value = fake_client
+ mock_paramiko.AutoAddPolicy.return_value = "autoadd"
+
+ test_ssh = ssh.SSH("admin", "example.net", pkey="key")
+ result = test_ssh.copy()
+ self.assertIsNot(test_ssh, result)
+
+ def test_close(self):
+ with mock.patch.object(self.test_client, "_client") as m_client:
+ self.test_client.close()
+ m_client.close.assert_called_once_with()
+ self.assertFalse(self.test_client._client)
+
+ @mock.patch("yardstick.ssh.six.moves.StringIO")
+ def test_execute(self, mock_string_io):
+ mock_string_io.side_effect = stdio = [mock.Mock(), mock.Mock()]
+ stdio[0].read.return_value = "stdout fake data"
+ stdio[1].read.return_value = "stderr fake data"
+ with mock.patch.object(self.test_client, "run", return_value=0)\
+ as mock_run:
+ status, stdout, stderr = self.test_client.execute(
+ "cmd",
+ stdin="fake_stdin",
+ timeout=43)
+ mock_run.assert_called_once_with(
+ "cmd", stdin="fake_stdin", stdout=stdio[0],
+ stderr=stdio[1], timeout=43, raise_on_error=False)
+ self.assertEqual(0, status)
+ self.assertEqual("stdout fake data", stdout)
+ self.assertEqual("stderr fake data", stderr)
+
+ @mock.patch("yardstick.ssh.time")
+ def test_wait_timeout(self, mock_time):
+ mock_time.time.side_effect = [1, 50, 150]
+ self.test_client.execute = mock.Mock(side_effect=[ssh.SSHError,
+ ssh.SSHError,
+ 0])
+ self.assertRaises(ssh.SSHTimeout, self.test_client.wait)
+ self.assertEqual([mock.call("uname")] * 2,
+ self.test_client.execute.mock_calls)
+
+ @mock.patch("yardstick.ssh.time")
+ def test_wait(self, mock_time):
+ mock_time.time.side_effect = [1, 50, 100]
+ self.test_client.execute = mock.Mock(side_effect=[ssh.SSHError,
+ ssh.SSHError,
+ 0])
+ self.test_client.wait()
+ self.assertEqual([mock.call("uname")] * 3,
+ self.test_client.execute.mock_calls)
+
+ @mock.patch("yardstick.ssh.paramiko")
+ def test_send_command(self, _):
+ paramiko_sshclient = self.test_client._get_client()
+ with mock.patch.object(paramiko_sshclient, "exec_command") \
+ as mock_paramiko_exec_command:
+ self.test_client.send_command('cmd')
+ mock_paramiko_exec_command.assert_called_once_with('cmd',
+ get_pty=True)
+
+
+class SSHRunTestCase(unittest.TestCase):
+ """Test SSH.run method in different aspects.
+
+ Also tested method "execute".
+ """
+
+ def setUp(self):
+ super(SSHRunTestCase, self).setUp()
+
+ self.fake_client = mock.Mock()
+ self.fake_session = mock.Mock()
+ self.fake_transport = mock.Mock()
+
+ self.fake_transport.open_session.return_value = self.fake_session
+ self.fake_client.get_transport.return_value = self.fake_transport
+
+ self.fake_session.recv_ready.return_value = False
+ self.fake_session.recv_stderr_ready.return_value = False
+ self.fake_session.send_ready.return_value = False
+ self.fake_session.exit_status_ready.return_value = True
+ self.fake_session.recv_exit_status.return_value = 0
+
+ self.test_client = ssh.SSH("admin", "example.net")
+ self.test_client._get_client = mock.Mock(return_value=self.fake_client)
+
+ @mock.patch("yardstick.ssh.select")
+ def test_execute(self, mock_select):
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.recv_ready.side_effect = [1, 0, 0]
+ self.fake_session.recv_stderr_ready.side_effect = [1, 0]
+ self.fake_session.recv.return_value = "ok"
+ self.fake_session.recv_stderr.return_value = "error"
+ self.fake_session.exit_status_ready.return_value = 1
+ self.fake_session.recv_exit_status.return_value = 127
+ self.assertEqual((127, "ok", "error"), self.test_client.execute("cmd"))
+ self.fake_session.exec_command.assert_called_once_with("cmd")
+
+ @mock.patch("yardstick.ssh.select")
+ def test_execute_args(self, mock_select):
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.recv_ready.side_effect = [1, 0, 0]
+ self.fake_session.recv_stderr_ready.side_effect = [1, 0]
+ self.fake_session.recv.return_value = "ok"
+ self.fake_session.recv_stderr.return_value = "error"
+ self.fake_session.exit_status_ready.return_value = 1
+ self.fake_session.recv_exit_status.return_value = 127
+
+ result = self.test_client.execute("cmd arg1 'arg2 with space'")
+ self.assertEqual((127, "ok", "error"), result)
+ self.fake_session.exec_command.assert_called_once_with(
+ "cmd arg1 'arg2 with space'")
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run(self, mock_select):
+ mock_select.select.return_value = ([], [], [])
+ self.assertEqual(0, self.test_client.run("cmd"))
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run_nonzero_status(self, mock_select):
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.recv_exit_status.return_value = 1
+ self.assertRaises(ssh.SSHError, self.test_client.run, "cmd")
+ self.assertEqual(1, self.test_client.run("cmd", raise_on_error=False))
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run_stdout(self, mock_select):
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.recv_ready.side_effect = [True, True, False]
+ self.fake_session.recv.side_effect = ["ok1", "ok2"]
+ stdout = mock.Mock()
+ self.test_client.run("cmd", stdout=stdout)
+ self.assertEqual([mock.call("ok1"), mock.call("ok2")],
+ stdout.write.mock_calls)
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run_stderr(self, mock_select):
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.recv_stderr_ready.side_effect = [True, False]
+ self.fake_session.recv_stderr.return_value = "error"
+ stderr = mock.Mock()
+ self.test_client.run("cmd", stderr=stderr)
+ stderr.write.assert_called_once_with("error")
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run_stdin(self, mock_select):
+ """Test run method with stdin.
+
+ Third send call was called with "e2" because only 3 bytes was sent
+ by second call. So remainig 2 bytes of "line2" was sent by third call.
+ """
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.exit_status_ready.side_effect = [0, 0, 0, True]
+ self.fake_session.send_ready.return_value = True
+ self.fake_session.send.side_effect = [5, 3, 2]
+ fake_stdin = mock.Mock()
+ fake_stdin.read.side_effect = ["line1", "line2", ""]
+ fake_stdin.closed = False
+
+ def close():
+ fake_stdin.closed = True
+ fake_stdin.close = mock.Mock(side_effect=close)
+ self.test_client.run("cmd", stdin=fake_stdin)
+ call = mock.call
+ send_calls = [call(encodeutils.safe_encode("line1", "utf-8")),
+ call(encodeutils.safe_encode("line2", "utf-8")),
+ call(encodeutils.safe_encode("e2", "utf-8"))]
+ self.assertEqual(send_calls, self.fake_session.send.mock_calls)
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run_stdin_keep_open(self, mock_select):
+ """Test run method with stdin.
+
+ Third send call was called with "e2" because only 3 bytes was sent
+ by second call. So remainig 2 bytes of "line2" was sent by third call.
+ """
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.exit_status_ready.side_effect = [0, 0, 0, True]
+ self.fake_session.send_ready.return_value = True
+ self.fake_session.send.side_effect = len
+ fake_stdin = StringIO(u"line1\nline2\n")
+ self.test_client.run("cmd", stdin=fake_stdin, keep_stdin_open=True)
+ call = mock.call
+ send_calls = [call(encodeutils.safe_encode("line1\nline2\n", "utf-8"))]
+ self.assertEqual(send_calls, self.fake_session.send.mock_calls)
+
+ @mock.patch("yardstick.ssh.select")
+ def test_run_select_error(self, mock_select):
+ self.fake_session.exit_status_ready.return_value = False
+ mock_select.select.return_value = ([], [], [True])
+ self.assertRaises(ssh.SSHError, self.test_client.run, "cmd")
+
+ @mock.patch("yardstick.ssh.time")
+ @mock.patch("yardstick.ssh.select")
+ def test_run_timemout(self, mock_select, mock_time):
+ mock_time.time.side_effect = [1, 3700]
+ mock_select.select.return_value = ([], [], [])
+ self.fake_session.exit_status_ready.return_value = False
+ self.assertRaises(ssh.SSHTimeout, self.test_client.run, "cmd")
+
+ @mock.patch("yardstick.ssh.open", create=True)
+ def test__put_file_shell(self, mock_open):
+ with mock.patch.object(self.test_client, "run") as run_mock:
+ self.test_client._put_file_shell("localfile", "remotefile", 0o42)
+ run_mock.assert_called_once_with(
+ 'cat > "remotefile"&& chmod -- 042 "remotefile"',
+ stdin=mock_open.return_value.__enter__.return_value)
+
+ @mock.patch("yardstick.ssh.open", create=True)
+ def test__put_file_shell_space(self, mock_open):
+ with mock.patch.object(self.test_client, "run") as run_mock:
+ self.test_client._put_file_shell("localfile",
+ "filename with space", 0o42)
+ run_mock.assert_called_once_with(
+ 'cat > "filename with space"&& chmod -- 042 "filename with '
+ 'space"',
+ stdin=mock_open.return_value.__enter__.return_value)
+
+ @mock.patch("yardstick.ssh.open", create=True)
+ def test__put_file_shell_tilde(self, mock_open):
+ with mock.patch.object(self.test_client, "run") as run_mock:
+ self.test_client._put_file_shell("localfile", "~/remotefile", 0o42)
+ run_mock.assert_called_once_with(
+ 'cat > ~/"remotefile"&& chmod -- 042 ~/"remotefile"',
+ stdin=mock_open.return_value.__enter__.return_value)
+
+ @mock.patch("yardstick.ssh.open", create=True)
+ def test__put_file_shell_tilde_spaces(self, mock_open):
+ with mock.patch.object(self.test_client, "run") as run_mock:
+ self.test_client._put_file_shell("localfile", "~/file with space",
+ 0o42)
+ run_mock.assert_called_once_with(
+ 'cat > ~/"file with space"&& chmod -- 042 ~/"file with space"',
+ stdin=mock_open.return_value.__enter__.return_value)
+
+ @mock.patch("yardstick.ssh.os.stat")
+ def test__put_file_sftp(self, mock_stat):
+ sftp = self.fake_client.open_sftp.return_value = mock.MagicMock()
+ sftp.__enter__.return_value = sftp
+
+ mock_stat.return_value = os.stat_result([0o753] + [0] * 9)
+
+ self.test_client._put_file_sftp("localfile", "remotefile")
+
+ sftp.put.assert_called_once_with("localfile", "remotefile")
+ mock_stat.assert_any_call("localfile")
+ sftp.chmod.assert_any_call("remotefile", 0o753)
+ sftp.__exit__.assert_called_once_with(None, None, None)
+
+ def test__put_file_sftp_mode(self):
+ sftp = self.fake_client.open_sftp.return_value = mock.MagicMock()
+ sftp.__enter__.return_value = sftp
+
+ self.test_client._put_file_sftp("localfile", "remotefile", mode=0o753)
+
+ sftp.put.assert_called_once_with("localfile", "remotefile")
+ sftp.chmod.assert_called_once_with("remotefile", 0o753)
+ sftp.__exit__.assert_called_once_with(None, None, None)
+
+ def test_put_file_SSHException(self):
+ exc = ssh.paramiko.SSHException
+ self.test_client._put_file_sftp = mock.Mock(side_effect=exc())
+ self.test_client._put_file_shell = mock.Mock()
+
+ self.test_client.put_file("foo", "bar", 42)
+ self.test_client._put_file_sftp.assert_called_once_with("foo", "bar",
+ mode=42)
+ self.test_client._put_file_shell.assert_called_once_with("foo", "bar",
+ mode=42)
+
+ def test_put_file_socket_error(self):
+ exc = socket.error
+ self.test_client._put_file_sftp = mock.Mock(side_effect=exc())
+ self.test_client._put_file_shell = mock.Mock()
+
+ self.test_client.put_file("foo", "bar", 42)
+ self.test_client._put_file_sftp.assert_called_once_with("foo", "bar",
+ mode=42)
+ self.test_client._put_file_shell.assert_called_once_with("foo", "bar",
+ mode=42)
+
+ @mock.patch("yardstick.ssh.os.stat")
+ def test_put_file_obj_with_mode(self, mock_stat):
+ sftp = self.fake_client.open_sftp.return_value = mock.MagicMock()
+ sftp.__enter__.return_value = sftp
+
+ mock_stat.return_value = os.stat_result([0o753] + [0] * 9)
+
+ self.test_client.put_file_obj("localfile", "remotefile", 'my_mode')
+
+ sftp.__enter__.assert_called_once()
+ sftp.putfo.assert_called_once_with("localfile", "remotefile")
+ sftp.chmod.assert_called_once_with("remotefile", 'my_mode')
+ sftp.__exit__.assert_called_once_with(None, None, None)
+
+
+class TestAutoConnectSSH(unittest.TestCase):
+
+ def test__connect_with_wait(self):
+ auto_connect_ssh = AutoConnectSSH('user1', 'host1', wait=True)
+ auto_connect_ssh._get_client = mock.Mock()
+ auto_connect_ssh.wait = mock_wait = mock.Mock()
+
+ auto_connect_ssh._connect()
+ self.assertEqual(mock_wait.call_count, 1)
+
+ def test__make_dict(self):
+ auto_connect_ssh = AutoConnectSSH('user1', 'host1')
+
+ expected = {
+ 'user': 'user1',
+ 'host': 'host1',
+ 'port': SSH.SSH_PORT,
+ 'pkey': None,
+ 'key_filename': None,
+ 'password': None,
+ 'name': None,
+ 'wait': True,
+ }
+ result = auto_connect_ssh._make_dict()
+ self.assertDictEqual(result, expected)
+
+ def test_get_class(self):
+ auto_connect_ssh = AutoConnectSSH('user1', 'host1')
+
+ self.assertEqual(auto_connect_ssh.get_class(), AutoConnectSSH)
+
+ @mock.patch('yardstick.ssh.SCPClient')
+ def test_put(self, mock_scp_client_type):
+ auto_connect_ssh = AutoConnectSSH('user1', 'host1')
+ auto_connect_ssh._client = mock.Mock()
+
+ auto_connect_ssh.put('a', 'z')
+ with mock_scp_client_type() as mock_scp_client:
+ self.assertEqual(mock_scp_client.put.call_count, 1)
+
+ @mock.patch('yardstick.ssh.SCPClient')
+ def test_get(self, mock_scp_client_type):
+ auto_connect_ssh = AutoConnectSSH('user1', 'host1')
+ auto_connect_ssh._client = mock.Mock()
+
+ auto_connect_ssh.get('a', 'z')
+ with mock_scp_client_type() as mock_scp_client:
+ self.assertEqual(mock_scp_client.get.call_count, 1)
+
+ def test_put_file(self):
+ auto_connect_ssh = AutoConnectSSH('user1', 'host1')
+ auto_connect_ssh._client = mock.Mock()
+ auto_connect_ssh._put_file_sftp = mock_put_sftp = mock.Mock()
+
+ auto_connect_ssh.put_file('a', 'b')
+ self.assertEqual(mock_put_sftp.call_count, 1)
+
+
+def main():
+ unittest.main()
+
+
+if __name__ == '__main__':
+ main()