This is an automated email from the ASF dual-hosted git repository. av pushed a commit to branch ignite-ducktape in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/ignite-ducktape by this push: new cbf8bdf IGNITE-13882 Add parametrization to ducktape install_root and persistence_root (#8600) cbf8bdf is described below commit cbf8bdfd05c316495d8e437e80d8fa92fd1088b3 Author: Ivan Daschinskiy <ivanda...@gmail.com> AuthorDate: Fri Dec 25 11:27:21 2020 +0300 IGNITE-13882 Add parametrization to ducktape install_root and persistence_root (#8600) --- .../ducktests/tests/ignitetest/services/ignite.py | 4 +- .../tests/ignitetest/services/ignite_app.py | 2 +- .../ducktests/tests/ignitetest/services/spark.py | 68 ++++---- .../ignitetest/services/utils/config_template.py | 6 +- .../ignitetest/services/utils/control_utility.py | 2 +- .../ignitetest/services/utils/ignite_aware.py | 54 +++++-- .../tests/ignitetest/services/utils/ignite_path.py | 56 ------- .../services/utils/ignite_persistence.py | 74 --------- .../tests/ignitetest/services/utils/ignite_spec.py | 82 ++++++---- .../tests/ignitetest/services/utils/jmx_utils.py | 15 +- .../tests/ignitetest/services/utils/path.py | 173 +++++++++++++++++++++ .../services/zk/templates/log4j.properties.j2 | 4 +- .../services/zk/templates/zookeeper.properties.j2 | 2 +- .../tests/ignitetest/services/zk/zookeeper.py | 78 ++++++---- .../ducktests/tests/ignitetest/tests/self_test.py | 39 +++++ modules/ducktests/tests/tox.ini | 1 - 16 files changed, 410 insertions(+), 250 deletions(-) diff --git a/modules/ducktests/tests/ignitetest/services/ignite.py b/modules/ducktests/tests/ignitetest/services/ignite.py index 7923dc7..af866b2 100644 --- a/modules/ducktests/tests/ignitetest/services/ignite.py +++ b/modules/ducktests/tests/ignitetest/services/ignite.py @@ -17,7 +17,6 @@ This module contains class to start ignite cluster node. """ -import os import re import signal from datetime import datetime @@ -32,7 +31,6 @@ class IgniteService(IgniteAwareService): Ignite node service. """ APP_SERVICE_CLASS = "org.apache.ignite.startup.cmdline.CommandLineStartup" - HEAP_DUMP_FILE = os.path.join(IgniteAwareService.PERSISTENT_ROOT, "ignite-heap.bin") # pylint: disable=R0913 def __init__(self, context, config, num_nodes, jvm_opts=None, startup_timeout_sec=60, shutdown_timeout_sec=10, @@ -42,7 +40,7 @@ class IgniteService(IgniteAwareService): def clean_node(self, node): node.account.kill_java_processes(self.APP_SERVICE_CLASS, clean_shutdown=False, allow_fail=True) - node.account.ssh("sudo rm -rf -- %s" % self.PERSISTENT_ROOT, allow_fail=False) + node.account.ssh("rm -rf -- %s" % self.persistent_root, allow_fail=False) def thread_dump(self, node): """ diff --git a/modules/ducktests/tests/ignitetest/services/ignite_app.py b/modules/ducktests/tests/ignitetest/services/ignite_app.py index 8833582..502eb56 100644 --- a/modules/ducktests/tests/ignitetest/services/ignite_app.py +++ b/modules/ducktests/tests/ignitetest/services/ignite_app.py @@ -76,7 +76,7 @@ class IgniteApplicationService(IgniteAwareService): node.account.kill_java_processes(self.servicejava_class_name, clean_shutdown=False, allow_fail=True) - node.account.ssh("rm -rf %s" % self.PERSISTENT_ROOT, allow_fail=False) + node.account.ssh("rm -rf -- %s" % self.persistent_root, allow_fail=False) def pids(self, node): return node.account.java_pids(self.servicejava_class_name) diff --git a/modules/ducktests/tests/ignitetest/services/spark.py b/modules/ducktests/tests/ignitetest/services/spark.py index 09e99ac..39ec199 100644 --- a/modules/ducktests/tests/ignitetest/services/spark.py +++ b/modules/ducktests/tests/ignitetest/services/spark.py @@ -18,25 +18,22 @@ This module contains spark service class. """ import os.path +from distutils.version import LooseVersion from ducktape.cluster.remoteaccount import RemoteCommandError from ducktape.services.background_thread import BackgroundThreadService -from ignitetest.services.utils.ignite_persistence import PersistenceAware +from ignitetest.services.utils.path import PathAware from ignitetest.services.utils.log_utils import monitor_log -class SparkService(BackgroundThreadService, PersistenceAware): +# pylint: disable=abstract-method +class SparkService(BackgroundThreadService, PathAware): """ Start a spark node. """ - INSTALL_DIR = "/opt/spark-{version}".format(version="2.3.4") - SPARK_PERSISTENT_ROOT = "/mnt/spark" - - logs = {} - # pylint: disable=R0913 - def __init__(self, context, num_nodes=3): + def __init__(self, context, num_nodes=3, version=LooseVersion("2.3.4")): """ :param context: test context :param num_nodes: number of Ignite nodes. @@ -44,16 +41,20 @@ class SparkService(BackgroundThreadService, PersistenceAware): super().__init__(context, num_nodes) self.log_level = "DEBUG" + self._version = version + self.init_logs_attribute() - for node in self.nodes: - self.logs["master_logs" + node.account.hostname] = { - "path": self.master_log_path(node), - "collect_default": True - } - self.logs["worker_logs" + node.account.hostname] = { - "path": self.slave_log_path(node), - "collect_default": True - } + @property + def project(self): + return "spark" + + @property + def version(self): + return self._version + + @property + def globals(self): + return self.context.globals def start(self, clean=True): BackgroundThreadService.start(self, clean=clean) @@ -69,14 +70,25 @@ class SparkService(BackgroundThreadService, PersistenceAware): else: script = "start-slave.sh spark://{spark_master}:7077".format(spark_master=self.nodes[0].account.hostname) - start_script = os.path.join(SparkService.INSTALL_DIR, "sbin", script) + start_script = os.path.join(self.home_dir, "sbin", script) - cmd = "export SPARK_LOG_DIR={spark_dir}; ".format(spark_dir=SparkService.SPARK_PERSISTENT_ROOT) - cmd += "export SPARK_WORKER_DIR={spark_dir}; ".format(spark_dir=SparkService.SPARK_PERSISTENT_ROOT) + cmd = "export SPARK_LOG_DIR={spark_dir}; ".format(spark_dir=self.persistent_root) + cmd += "export SPARK_WORKER_DIR={spark_dir}; ".format(spark_dir=self.persistent_root) cmd += "{start_script} &".format(start_script=start_script) return cmd + def init_logs_attribute(self): + for node in self.nodes: + self.logs["master_logs" + node.account.hostname] = { + "path": self.master_log_path(node), + "collect_default": True + } + self.logs["worker_logs" + node.account.hostname] = { + "path": self.slave_log_path(node), + "collect_default": True + } + def start_node(self, node): self.init_persistent(node) @@ -103,9 +115,9 @@ class SparkService(BackgroundThreadService, PersistenceAware): def stop_node(self, node): if node == self.nodes[0]: - node.account.ssh(os.path.join(SparkService.INSTALL_DIR, "sbin", "stop-master.sh")) + node.account.ssh(os.path.join(self.home_dir, "sbin", "stop-master.sh")) else: - node.account.ssh(os.path.join(SparkService.INSTALL_DIR, "sbin", "stop-slave.sh")) + node.account.ssh(os.path.join(self.home_dir, "sbin", "stop-slave.sh")) def clean_node(self, node): """ @@ -113,7 +125,7 @@ class SparkService(BackgroundThreadService, PersistenceAware): """ node.account.kill_java_processes(self.java_class_name(node), clean_shutdown=False, allow_fail=True) - node.account.ssh("sudo rm -rf -- %s" % SparkService.SPARK_PERSISTENT_ROOT, allow_fail=False) + node.account.ssh("rm -rf -- %s" % self.persistent_root, allow_fail=False) def pids(self, node): """ @@ -135,26 +147,24 @@ class SparkService(BackgroundThreadService, PersistenceAware): return "org.apache.spark.deploy.worker.Worker" - @staticmethod - def master_log_path(node): + def master_log_path(self, node): """ :param node: Spark master node. :return: Path to log file. """ return "{SPARK_LOG_DIR}/spark-{userID}-org.apache.spark.deploy.master.Master-{instance}-{host}.out".format( - SPARK_LOG_DIR=SparkService.SPARK_PERSISTENT_ROOT, + SPARK_LOG_DIR=self.persistent_root, userID=node.account.user, instance=1, host=node.account.hostname) - @staticmethod - def slave_log_path(node): + def slave_log_path(self, node): """ :param node: Spark slave node. :return: Path to log file. """ return "{SPARK_LOG_DIR}/spark-{userID}-org.apache.spark.deploy.worker.Worker-{instance}-{host}.out".format( - SPARK_LOG_DIR=SparkService.SPARK_PERSISTENT_ROOT, + SPARK_LOG_DIR=self.persistent_root, userID=node.account.user, instance=1, host=node.account.hostname) diff --git a/modules/ducktests/tests/ignitetest/services/utils/config_template.py b/modules/ducktests/tests/ignitetest/services/utils/config_template.py index 875f12b..91c279f 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/config_template.py +++ b/modules/ducktests/tests/ignitetest/services/utils/config_template.py @@ -20,8 +20,8 @@ import os from jinja2 import FileSystemLoader, Environment -DEFAULT_CONFIG_PATH = os.path.dirname(os.path.abspath(__file__)) + "/templates" -DEFAULT_IGNITE_CONF = DEFAULT_CONFIG_PATH + "/ignite.xml.j2" +DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates") +DEFAULT_IGNITE_CONF = os.path.join(DEFAULT_CONFIG_PATH, "ignite.xml.j2") class ConfigTemplate: @@ -69,4 +69,4 @@ class IgniteLoggerConfigTemplate(ConfigTemplate): Ignite logger configuration. """ def __init__(self): - super().__init__(DEFAULT_CONFIG_PATH + "/log4j.xml.j2") + super().__init__(os.path.join(DEFAULT_CONFIG_PATH, "log4j.xml.j2")) diff --git a/modules/ducktests/tests/ignitetest/services/utils/control_utility.py b/modules/ducktests/tests/ignitetest/services/utils/control_utility.py index 90b6549..9b1a9ea 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/control_utility.py +++ b/modules/ducktests/tests/ignitetest/services/utils/control_utility.py @@ -264,7 +264,7 @@ class ControlUtility: return output def __form_cmd(self, node, cmd): - return self._cluster.spec.path.script(f"{self.BASE_COMMAND} --host {node.account.externally_routable_ip} {cmd}") + return self._cluster.script(f"{self.BASE_COMMAND} --host {node.account.externally_routable_ip} {cmd}") @staticmethod def __parse_output(raw_output): diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py index 06fbe32..cf0e9c7 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py +++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_aware.py @@ -29,19 +29,18 @@ from ducktape.services.background_thread import BackgroundThreadService from ducktape.utils.util import wait_until from ignitetest.services.utils.concurrent import CountDownLatch, AtomicValue -from ignitetest.services.utils.ignite_persistence import IgnitePersistenceAware +from ignitetest.services.utils.path import IgnitePathAware from ignitetest.services.utils.ignite_spec import resolve_spec from ignitetest.services.utils.jmx_utils import ignite_jmx_mixin from ignitetest.services.utils.log_utils import monitor_log -class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metaclass=ABCMeta): +# pylint: disable=too-many-public-methods +class IgniteAwareService(BackgroundThreadService, IgnitePathAware, metaclass=ABCMeta): """ The base class to build services aware of Ignite. """ - NETFILTER_STORE_PATH = os.path.join(IgnitePersistenceAware.TEMP_DIR, "iptables.bak") - # pylint: disable=R0913 def __init__(self, context, config, num_nodes, startup_timeout_sec, shutdown_timeout_sec, **kwargs): """ @@ -58,10 +57,23 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl self.shutdown_timeout_sec = shutdown_timeout_sec self.spec = resolve_spec(self, context, config, **kwargs) + self.init_logs_attribute() self.disconnected_nodes = [] self.killed = False + @property + def version(self): + return self.config.version + + @property + def project(self): + return self.spec.project + + @property + def globals(self): + return self.context.globals + def start_async(self, clean=True): """ Starts in async way. @@ -89,7 +101,7 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl wait_until(lambda: self.alive(node), timeout_sec=10) - ignite_jmx_mixin(node, self.pids(node)) + ignite_jmx_mixin(node, self.spec, self.pids(node)) def stop_async(self): """ @@ -159,7 +171,7 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl node_config = self._prepare_config(node) - node.account.create_file(self.CONFIG_FILE, node_config) + node.account.create_file(self.config_file, node_config) def _prepare_config(self, node): if not self.config.consistent_id: @@ -171,7 +183,7 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl config.discovery_spi.prepare_on_start(cluster=self) - node_config = self.spec.config_template.render(config_dir=self.PERSISTENT_ROOT, work_dir=self.WORK_DIR, + node_config = self.spec.config_template.render(config_dir=self.persistent_root, work_dir=self.work_dir, config=config) setattr(node, "consistent_id", node.account.externally_routable_ip) @@ -190,7 +202,7 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl # pylint: disable=W0613 def _worker(self, idx, node): - cmd = self.spec.command(node.log_file) + cmd = self.spec.command(node) self.logger.debug("Attempting to start Application Service on %s with command: %s" % (str(node.account), cmd)) @@ -277,6 +289,13 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl task(node) + @property + def netfilter_store_path(self): + """ + :return: path to store backup of iptables filter + """ + return os.path.join(self.temp_dir, "iptables.bak") + def drop_network(self, nodes=None): """ Disconnects node from cluster. @@ -311,12 +330,12 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl def __backup_iptables(self, nodes): # Store current network filter settings. for node in nodes: - cmd = "sudo iptables-save | tee " + IgniteAwareService.NETFILTER_STORE_PATH + cmd = f"sudo iptables-save | tee {self.netfilter_store_path}" exec_error = str(node.account.ssh_client.exec_command(cmd)[2].read(), sys.getdefaultencoding()) if "Warning: iptables-legacy tables present" in exec_error: - cmd = "sudo iptables-legacy-save | tee " + IgniteAwareService.NETFILTER_STORE_PATH + cmd = f"sudo iptables-legacy-save | tee {self.netfilter_store_path}" exec_error = str(node.account.ssh_client.exec_command(cmd)[2].read(), sys.getdefaultencoding()) @@ -330,7 +349,7 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl def __restore_iptables(self): # Restore previous network filter settings. - cmd = "sudo iptables-restore < " + IgniteAwareService.NETFILTER_STORE_PATH + cmd = f"sudo iptables-restore < {self.netfilter_store_path}" errors = [] @@ -359,8 +378,13 @@ class IgniteAwareService(BackgroundThreadService, IgnitePersistenceAware, metacl """ Update the node log file. """ - cnt = list(node.account.ssh_capture(f'ls {self.LOGS_DIR} | ' - f'grep -E "^console_[0-9]*.log$" | ' - f'wc -l', callback=int))[0] + if not hasattr(node, 'log_file'): + node.log_file = os.path.join(self.log_dir, "console.log") - node.log_file = self.STDOUT_STDERR_CAPTURE.replace('.log', f'_{cnt + 1}.log') + cnt = list(node.account.ssh_capture(f'ls {self.log_dir} | ' + f'grep -E "^console.log(.[0-9]+)?$" | ' + f'wc -l', callback=int))[0] + if cnt > 0: + rotated_log = os.path.join(self.log_dir, f"console.log.{cnt}") + self.logger.debug(f"rotating {node.log_file} to {rotated_log} on {node.name}") + node.account.ssh(f"mv {node.log_file} {rotated_log}") diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_path.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_path.py deleted file mode 100644 index 4089112..0000000 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_path.py +++ /dev/null @@ -1,56 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. - -""" -This module contains ignite path resolve utilities. -""" - -import os - - -class IgnitePath: - """Path resolver for Ignite system tests which assumes the following layout: - - /opt/ignite-dev # Current version of Ignite under test - /opt/ignite-2.7.6 # Example of an older version of Ignite installed from tarball - /opt/ignite-<version> # Other previous versions of Ignite - ... - """ - SCRATCH_ROOT = "/mnt" - IGNITE_INSTALL_ROOT = "/opt" - - def __init__(self, version, project="ignite"): - self.version = version - home_dir = "%s-%s" % (project, str(self.version)) - self.home = os.path.join(IgnitePath.IGNITE_INSTALL_ROOT, home_dir) - - def module(self, module_name): - """ - :param module_name: name of Ignite optional lib - :return: absolute path to the specified module - """ - if self.version.is_dev: - module_path = os.path.join("modules", module_name, "target") - else: - module_path = os.path.join("libs", "optional", "ignite-%s" % module_name) - - return os.path.join(self.home, module_path) - - def script(self, script_name): - """ - :param script_name: name of Ignite script - :return: absolute path to the specified script - """ - return os.path.join(self.home, "bin", script_name) diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_persistence.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_persistence.py deleted file mode 100644 index 4e018cc..0000000 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_persistence.py +++ /dev/null @@ -1,74 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. - -""" -This module contains classes that represent persistent artifacts of tests -""" - -import os - -from ignitetest.services.utils.config_template import IgniteLoggerConfigTemplate - - -class PersistenceAware: - """ - This class contains basic persistence artifacts - """ - # Root directory for persistent output - PERSISTENT_ROOT = "/mnt/service" - TEMP_DIR = os.path.join(PERSISTENT_ROOT, "tmp") - LOGS_DIR = os.path.join(PERSISTENT_ROOT, "logs") - STDOUT_STDERR_CAPTURE = os.path.join(LOGS_DIR, "console.log") - - logs = { - "console_log": { - "path": LOGS_DIR, - "collect_default": True - } - } - - def init_persistent(self, node): - """ - Init persistent directory. - :param node: Service node. - """ - node.account.mkdirs(self.PERSISTENT_ROOT) - node.account.mkdirs(self.TEMP_DIR) - node.account.mkdirs(self.LOGS_DIR) - - -class IgnitePersistenceAware(PersistenceAware): - """ - This class contains Ignite persistence artifacts - """ - WORK_DIR = os.path.join(PersistenceAware.PERSISTENT_ROOT, "work") - CONFIG_FILE = os.path.join(PersistenceAware.PERSISTENT_ROOT, "ignite-config.xml") - LOG4J_CONFIG_FILE = os.path.join(PersistenceAware.PERSISTENT_ROOT, "ignite-log4j.xml") - - def __getattribute__(self, item): - if item == 'logs': - return PersistenceAware.logs - - return super().__getattribute__(item) - - def init_persistent(self, node): - """ - Init persistent directory. - :param node: Ignite service node. - """ - super().init_persistent(node) - - logger_config = IgniteLoggerConfigTemplate().render(work_dir=self.WORK_DIR) - node.account.create_file(self.LOG4J_CONFIG_FILE, logger_config) diff --git a/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py b/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py index 6b9ba9f..489c887 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py +++ b/modules/ducktests/tests/ignitetest/services/utils/ignite_spec.py @@ -20,11 +20,11 @@ This module contains Spec classes that describes config and command line to star import base64 import importlib import json +import os from abc import ABCMeta, abstractmethod from ignitetest.services.utils.config_template import IgniteClientConfigTemplate, IgniteServerConfigTemplate -from ignitetest.services.utils.ignite_path import IgnitePath -from ignitetest.services.utils.ignite_persistence import IgnitePersistenceAware +from ignitetest.services.utils.path import get_home_dir, get_module_path from ignitetest.utils.version import DEV_BRANCH @@ -46,10 +46,11 @@ def resolve_spec(service, context, config, **kwargs): return len(impl_filter) > 0 if is_impl("IgniteService"): - return _resolve_spec("NodeSpec", ApacheIgniteNodeSpec)(config=config, **kwargs) + return _resolve_spec("NodeSpec", ApacheIgniteNodeSpec)(path_aware=service, config=config, **kwargs) if is_impl("IgniteApplicationService"): - return _resolve_spec("AppSpec", ApacheIgniteApplicationSpec)(context=context, config=config, **kwargs) + return _resolve_spec("AppSpec", ApacheIgniteApplicationSpec)(path_aware=service, context=context, + config=config, **kwargs) raise Exception("There is no specification for class %s" % type(service)) @@ -58,12 +59,13 @@ class IgniteSpec(metaclass=ABCMeta): """ This class is a basic Spec """ - def __init__(self, config, project, jvm_opts): - self.version = config.version - self.path = IgnitePath(self.version, project) + def __init__(self, path_aware, config, project, jvm_opts): + self.project = project + self.path_aware = path_aware self.envs = {} self.jvm_opts = jvm_opts or [] self.config = config + self.version = config.version @property def config_template(self): @@ -74,8 +76,22 @@ class IgniteSpec(metaclass=ABCMeta): return IgniteClientConfigTemplate() return IgniteServerConfigTemplate() + def __home(self, version=None): + """ + Get home directory for current spec. + """ + version = version if version else self.version + return get_home_dir(self.path_aware.install_root, self.project, version) + + def _module(self, name): + """ + Get module path for current spec. + """ + version = DEV_BRANCH if name == "ducktests" else self.version + return get_module_path(self.__home(version), name, version) + @abstractmethod - def command(self): + def command(self, node): """ :return: string that represents command to run service on a node """ @@ -95,23 +111,22 @@ class IgniteSpec(metaclass=ABCMeta): return " ".join(opts) -class IgniteNodeSpec(IgniteSpec, IgnitePersistenceAware): +class IgniteNodeSpec(IgniteSpec): """ Spec to run ignite node """ - # pylint: disable=W0221 - def command(self, stdout_stderr): + def command(self, node): cmd = "%s %s %s %s 2>&1 | tee -a %s &" % \ (self._envs(), - self.path.script("ignite.sh"), + self.path_aware.script("ignite.sh"), self._jvm_opts(), - self.CONFIG_FILE, - stdout_stderr) + self.path_aware.config_file, + node.log_file) return cmd -class IgniteApplicationSpec(IgniteSpec, IgnitePersistenceAware): +class IgniteApplicationSpec(IgniteSpec): """ Spec to run ignite application """ @@ -123,18 +138,18 @@ class IgniteApplicationSpec(IgniteSpec, IgnitePersistenceAware): return ",".join(self.args) # pylint: disable=W0221 - def command(self, stdout_stderr): + def command(self, node): cmd = "%s %s %s %s 2>&1 | tee -a %s &" % \ (self._envs(), - self.path.script("ignite.sh"), + self.path_aware.script("ignite.sh"), self._jvm_opts(), self._app_args(), - stdout_stderr) + node.log_file) return cmd -class ApacheIgniteNodeSpec(IgniteNodeSpec, IgnitePersistenceAware): +class ApacheIgniteNodeSpec(IgniteNodeSpec): """ Implementation IgniteNodeSpec for Apache Ignite project """ @@ -143,24 +158,24 @@ class ApacheIgniteNodeSpec(IgniteNodeSpec, IgnitePersistenceAware): libs = (modules or []) libs.append("log4j") - libs = list(map(lambda m: self.path.module(m) + "/*", libs)) + libs = list(map(lambda m: os.path.join(self._module(m), "*"), libs)) - libs.append(IgnitePath(DEV_BRANCH).module("ducktests") + "/*") + libs.append(os.path.join(self._module("ducktests"), "*")) self.envs = { 'EXCLUDE_TEST_CLASSES': 'true', - 'IGNITE_LOG_DIR': self.PERSISTENT_ROOT, + 'IGNITE_LOG_DIR': self.path_aware.persistent_root, 'USER_LIBS': ":".join(libs) } self.jvm_opts.extend([ - "-DIGNITE_SUCCESS_FILE=" + self.PERSISTENT_ROOT + "/success_file", - "-Dlog4j.configuration=file:" + self.LOG4J_CONFIG_FILE, + "-DIGNITE_SUCCESS_FILE=" + os.path.join(self.path_aware.persistent_root, "success_file"), + "-Dlog4j.configuration=file:" + self.path_aware.log_config_file, "-Dlog4j.configDebug=true" ]) -class ApacheIgniteApplicationSpec(IgniteApplicationSpec, IgnitePersistenceAware): +class ApacheIgniteApplicationSpec(IgniteApplicationSpec): """ Implementation IgniteApplicationSpec for Apache Ignite project """ @@ -172,20 +187,20 @@ class ApacheIgniteApplicationSpec(IgniteApplicationSpec, IgnitePersistenceAware) libs = modules or [] libs.extend(["log4j"]) - libs = [self.path.module(m) + "/*" for m in libs] - libs.append(IgnitePath(DEV_BRANCH).module("ducktests") + "/*") + libs = list(map(lambda m: os.path.join(self._module(m), "*"), libs)) + libs.append(os.path.join(self._module("ducktests"), "*")) libs.extend(self.__jackson()) self.envs = { "MAIN_CLASS": servicejava_class_name, "EXCLUDE_TEST_CLASSES": "true", - "IGNITE_LOG_DIR": self.PERSISTENT_ROOT, + "IGNITE_LOG_DIR": self.path_aware.persistent_root, "USER_LIBS": ":".join(libs) } self.jvm_opts.extend([ - "-DIGNITE_SUCCESS_FILE=" + self.PERSISTENT_ROOT + "/success_file", - "-Dlog4j.configuration=file:" + self.LOG4J_CONFIG_FILE, + "-DIGNITE_SUCCESS_FILE=" + os.path.join(self.path_aware.persistent_root, "success_file"), + "-Dlog4j.configuration=file:" + self.path_aware.log_config_file, "-Dlog4j.configDebug=true", "-DIGNITE_NO_SHUTDOWN_HOOK=true", # allows to perform operations on app termination. "-Xmx1G", @@ -196,13 +211,14 @@ class ApacheIgniteApplicationSpec(IgniteApplicationSpec, IgnitePersistenceAware) self.args = [ str(start_ignite), java_class_name, - self.CONFIG_FILE, + self.path_aware.config_file, str(base64.b64encode(json.dumps(params).encode('utf-8')), 'utf-8') ] def __jackson(self): - if not self.version.is_dev: - aws = self.path.module("aws") + version = self.version + if not version.is_dev: + aws = self._module("aws") return self.context.cluster.nodes[0].account.ssh_capture( "ls -d %s/* | grep jackson | tr '\n' ':' | sed 's/.$//'" % aws) diff --git a/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py b/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py index 925034b..a14d96d 100644 --- a/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py +++ b/modules/ducktests/tests/ignitetest/services/utils/jmx_utils.py @@ -17,19 +17,20 @@ This module contains JMX Console client and different utilities and mixins to retrieve ignite node parameters and attributes. """ - +import os import re from ignitetest.services.utils.decorators import memoize -def ignite_jmx_mixin(node, pids): +def ignite_jmx_mixin(node, spec, pids): """ Dynamically mixin JMX attributes to Ignite service node. :param node: Ignite service node. :param pids: Ignite service node pids. """ setattr(node, 'pids', pids) + setattr(node, 'spec', spec) base_cls = node.__class__ base_cls_name = node.__class__.__name__ node.__class__ = type(base_cls_name, (base_cls, IgniteJmxMixin), {}) @@ -55,12 +56,18 @@ class JmxMBean: class JmxClient: """JMX client, invokes jmxterm on node locally. """ - jmx_util_cmd = 'java -jar /opt/jmxterm.jar -v silent -n' - def __init__(self, node): self.node = node + self.install_root = node.spec.path_aware.install_root self.pid = node.pids[0] + @property + def jmx_util_cmd(self): + """ + :return: jmxterm prepared command line invocation. + """ + return os.path.join(f"java -jar {self.install_root}/jmxterm.jar -v silent -n") + @memoize def find_mbean(self, pattern, domain='org.apache'): """ diff --git a/modules/ducktests/tests/ignitetest/services/utils/path.py b/modules/ducktests/tests/ignitetest/services/utils/path.py new file mode 100644 index 0000000..9cab69d --- /dev/null +++ b/modules/ducktests/tests/ignitetest/services/utils/path.py @@ -0,0 +1,173 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +""" +This module contains classes that represent persistent artifacts of tests +""" + +import os +from abc import abstractmethod, ABCMeta + +from ignitetest.services.utils.config_template import IgniteLoggerConfigTemplate + + +def get_home_dir(install_root, project, version): + """ + Get path to binary release (home) directory depending on version. + """ + return os.path.join(install_root, f"{project}-{version}") + + +def get_module_path(project_dir, module_name, version): + """ + Get absolute path to the specified module. + """ + if version.is_dev: + module_path = os.path.join("modules", module_name, "target") + else: + module_path = os.path.join("libs", "optional", "ignite-%s" % module_name) + + return os.path.join(project_dir, module_path) + + +class PathAware: + """ + Basic class for path configs. + """ + def init_persistent(self, node): + """ + Init persistent directory. + :param node: Service node. + """ + node.account.mkdirs(f"{self.persistent_root} {self.temp_dir} {self.work_dir} {self.log_dir}") + + def init_logs_attribute(self): + """ + Initialize logs attribute for collecting logs by ducktape. + After changing to property based logs, will be removed. + """ + setattr(self, 'logs', { + "logs": { + "path": self.log_dir, + "collect_default": True + } + }) + + @property + @abstractmethod + def config_file(self): + """ + :return: path to project configuration file + """ + + @property + @abstractmethod + def log_config_file(self): + """ + :return: path to logger configuration file + """ + + @property + def work_dir(self): + """ + :return: path to work directory + """ + return os.path.join(self.persistent_root, "work") + + @property + def log_dir(self): + """ + :return: path to log directory + """ + return os.path.join(self.persistent_root, "logs") + + @property + @abstractmethod + def project(self): + """ + :return: project name, for example 'zookeeper' for Apache Zookeeper. + """ + + @property + @abstractmethod + def version(self): + """ + :return: version of project. + """ + + @property + @abstractmethod + def globals(self): + """ + :return: dictionary of globals variable (usually from test context). + """ + + @property + def home_dir(self): + """ + :return: path to binary release (home) directory + """ + return get_home_dir(self.install_root, self.project, self.version) + + @property + def temp_dir(self): + """ + :return: path to temp directory + """ + return os.path.join(self.persistent_root, "tmp") + + @property + def persistent_root(self): + """ + :return: path to persistent root + """ + return self.globals.get("persistent_root", "/mnt/service") + + @property + def install_root(self): + """ + :return: path to distributive installation root + """ + return self.globals.get("install_root", "/opt") + + +class IgnitePathAware(PathAware, metaclass=ABCMeta): + """ + This class contains Ignite path configs. + """ + def init_persistent(self, node): + """ + Init persistent directory. + :param node: Ignite service node. + """ + super().init_persistent(node) + + logger_config = IgniteLoggerConfigTemplate().render(work_dir=self.work_dir) + node.account.create_file(self.log_config_file, logger_config) + + @property + def config_file(self): + return os.path.join(self.persistent_root, "ignite-config.xml") + + @property + def log_config_file(self): + return os.path.join(self.persistent_root, "ignite-log4j.xml") + + def script(self, script_name): + """ + :param script_name: name of Ignite script + :return: absolute path to the specified script + """ + return os.path.join(self.home_dir, "bin", script_name) diff --git a/modules/ducktests/tests/ignitetest/services/zk/templates/log4j.properties.j2 b/modules/ducktests/tests/ignitetest/services/zk/templates/log4j.properties.j2 index 507ec98..1f51c94 100644 --- a/modules/ducktests/tests/ignitetest/services/zk/templates/log4j.properties.j2 +++ b/modules/ducktests/tests/ignitetest/services/zk/templates/log4j.properties.j2 @@ -18,8 +18,8 @@ zookeeper.root.logger=INFO, FILE zookeeper.console.threshold=INFO -zookeeper.log.dir={{ PERSISTENT_ROOT }} -zookeeper.log.file=zookeeper.log +zookeeper.log.dir={{ log_dir }} +zookeeper.log.file={{ LOG_FILENAME }} zookeeper.log.threshold=INFO zookeeper.log.maxfilesize=256MB zookeeper.log.maxbackupindex=20 diff --git a/modules/ducktests/tests/ignitetest/services/zk/templates/zookeeper.properties.j2 b/modules/ducktests/tests/ignitetest/services/zk/templates/zookeeper.properties.j2 index 2ea8c3d..8a4e7b8 100644 --- a/modules/ducktests/tests/ignitetest/services/zk/templates/zookeeper.properties.j2 +++ b/modules/ducktests/tests/ignitetest/services/zk/templates/zookeeper.properties.j2 @@ -19,7 +19,7 @@ tickTime={{ settings.tick_time }} minSessionTimeout={{ settings.min_session_timeout }} initLimit={{ settings.init_limit }} syncLimit={{ settings.sync_limit }} -dataDir={{ DATA_DIR }} +dataDir={{ data_dir }} clientPort={{ settings.client_port }} {% for node in nodes %} server.{{ loop.index }}={{ node.account.hostname }}:2888:3888 diff --git a/modules/ducktests/tests/ignitetest/services/zk/zookeeper.py b/modules/ducktests/tests/ignitetest/services/zk/zookeeper.py index baf8848..b76ea99 100644 --- a/modules/ducktests/tests/ignitetest/services/zk/zookeeper.py +++ b/modules/ducktests/tests/ignitetest/services/zk/zookeeper.py @@ -18,11 +18,13 @@ This module contains classes and utilities to start zookeeper cluster for testin """ import os.path +from distutils.version import LooseVersion from ducktape.services.service import Service from ducktape.utils.util import wait_until from ignitetest.services.utils.log_utils import monitor_log +from ignitetest.services.utils.path import PathAware class ZookeeperSettings: @@ -36,32 +38,48 @@ class ZookeeperSettings: self.sync_limit = kwargs.get('sync_limit', 5) self.client_port = kwargs.get('client_port', 2181) + version = kwargs.get("version") + if version: + if isinstance(version, str): + version = LooseVersion(version) + self.version = version + else: + self.version = LooseVersion("3.5.8") + assert self.tick_time <= self.min_session_timeout // 2, "'tick_time' must be <= 'min_session_timeout' / 2" -class ZookeeperService(Service): +class ZookeeperService(Service, PathAware): """ Zookeeper service. """ - PERSISTENT_ROOT = "/mnt/zookeeper" - CONFIG_ROOT = os.path.join(PERSISTENT_ROOT, "conf") - LOG_FILE = os.path.join(PERSISTENT_ROOT, "zookeeper.log") - DATA_DIR = os.path.join(PERSISTENT_ROOT, "data") - CONFIG_FILE = os.path.join(CONFIG_ROOT, "zookeeper.properties") - LOG_CONFIG_FILE = os.path.join(CONFIG_ROOT, "log4j.properties") - ZK_LIB_DIR = "/opt/zookeeper-3.5.8/lib" - - logs = { - "zk_log": { - "path": LOG_FILE, - "collect_default": True - } - } + LOG_FILENAME = "zookeeper.log" def __init__(self, context, num_nodes, settings=ZookeeperSettings(), start_timeout_sec=60): super().__init__(context, num_nodes) self.settings = settings self.start_timeout_sec = start_timeout_sec + self.init_logs_attribute() + + @property + def version(self): + return self.settings.version + + @property + def globals(self): + return self.context.globals + + @property + def log_config_file(self): + return os.path.join(self.persistent_root, "log4j.properties") + + @property + def config_file(self): + return os.path.join(self.persistent_root, "zookeeper.properties") + + @property + def project(self): + return "zookeeper" def start(self, clean=True): super().start(clean=clean) @@ -77,19 +95,18 @@ class ZookeeperService(Service): self.logger.info("Starting Zookeeper node %d on %s", idx, node.account.hostname) - node.account.ssh("mkdir -p %s" % self.DATA_DIR) - node.account.ssh("mkdir -p %s" % self.CONFIG_ROOT) - node.account.ssh("echo %d > %s/myid" % (idx, self.DATA_DIR)) + self.init_persistent(node) + node.account.ssh(f"echo {idx} > {self.work_dir}/myid") - config_file = self.render('zookeeper.properties.j2', settings=self.settings) - node.account.create_file(self.CONFIG_FILE, config_file) + config_file = self.render('zookeeper.properties.j2', settings=self.settings, data_dir=self.work_dir) + node.account.create_file(self.config_file, config_file) self.logger.info("ZK config %s", config_file) - log_config_file = self.render('log4j.properties.j2') - node.account.create_file(self.LOG_CONFIG_FILE, log_config_file) + log_config_file = self.render('log4j.properties.j2', log_dir=self.log_dir) + node.account.create_file(self.log_config_file, log_config_file) - start_cmd = "nohup java -cp %s/*:%s org.apache.zookeeper.server.quorum.QuorumPeerMain %s >/dev/null 2>&1 &" % \ - (self.ZK_LIB_DIR, self.CONFIG_ROOT, self.CONFIG_FILE) + start_cmd = f"nohup java -cp {os.path.join(self.home_dir, 'lib')}/*:{self.persistent_root} " \ + f"org.apache.zookeeper.server.quorum.QuorumPeerMain {self.config_file} >/dev/null 2>&1 &" node.account.ssh(start_cmd) @@ -104,13 +121,20 @@ class ZookeeperService(Service): :param node: Zookeeper service node. :param timeout: Wait timeout. """ - with monitor_log(node, self.LOG_FILE, from_the_beginning=True) as monitor: + with monitor_log(node, self.log_file, from_the_beginning=True) as monitor: monitor.wait_until( "LEADER ELECTION TOOK", timeout_sec=timeout, - err_msg="Zookeeper quorum was not formed on %s" % node.account.hostname + err_msg=f"Zookeeper quorum was not formed on {node.account.hostname}" ) + @property + def log_file(self): + """ + :return: current log file of node. + """ + return os.path.join(self.log_dir, self.LOG_FILENAME) + @staticmethod def java_class_name(): """ The class name of the Zookeeper quorum peers. """ @@ -150,7 +174,7 @@ class ZookeeperService(Service): self.logger.warn("%s %s was still alive at cleanup time. Killing forcefully..." % (self.__class__.__name__, node.account)) node.account.kill_process("zookeeper", clean_shutdown=False, allow_fail=True) - node.account.ssh("rm -rf %s %s %s" % (self.CONFIG_ROOT, self.DATA_DIR, self.LOG_FILE), allow_fail=False) + node.account.ssh(f"rm -rf -- {self.persistent_root}", allow_fail=False) def kill(self): """ diff --git a/modules/ducktests/tests/ignitetest/tests/self_test.py b/modules/ducktests/tests/ignitetest/tests/self_test.py index e5f91e9..1d6e6ea 100644 --- a/modules/ducktests/tests/ignitetest/tests/self_test.py +++ b/modules/ducktests/tests/ignitetest/tests/self_test.py @@ -16,6 +16,7 @@ """ This module contains smoke tests that checks that ducktape works as expected """ +import os from ignitetest.services.ignite import IgniteService from ignitetest.services.ignite_app import IgniteApplicationService @@ -93,3 +94,41 @@ class SelfTest(IgniteTest): client.stop() ignites.stop() + + @cluster(num_nodes=1) + @ignite_versions(str(DEV_BRANCH)) + def test_logs_rotation(self, ignite_version): + """ + Test logs rotation after ignite service restart. + """ + def get_log_lines_count(service, filename): + node = service.nodes[0] + log_file = os.path.join(service.log_dir, filename) + log_cnt = list(node.account.ssh_capture(f'cat {log_file} | wc -l', callback=int))[0] + return log_cnt + + def get_logs_count(service): + node = service.nodes[0] + return list(node.account.ssh_capture(f'ls {service.log_dir} | wc -l', callback=int))[0] + + ignites = IgniteService(self.test_context, IgniteConfiguration(version=IgniteVersion(ignite_version)), + num_nodes=1) + + ignites.start() + + num_restarts = 6 + for i in range(num_restarts - 1): + ignites.stop() + + old_cnt = get_log_lines_count(ignites, "console.log") + assert old_cnt > 0 + + ignites.start(clean=False) + + new_cnt = get_log_lines_count(ignites, "console.log") + assert new_cnt > 0 + + # check that there is no new entry in rotated file + assert old_cnt == get_log_lines_count(ignites, f"console.log.{i + 1}") + + assert get_logs_count(ignites) == num_restarts diff --git a/modules/ducktests/tests/tox.ini b/modules/ducktests/tests/tox.ini index f74d685..02b773b 100644 --- a/modules/ducktests/tests/tox.ini +++ b/modules/ducktests/tests/tox.ini @@ -26,7 +26,6 @@ python = envdir = {homedir}/.virtualenvs/ignite-ducktests-{envname} deps = -r ./docker/requirements-dev.txt -recreate = True usedevelop = True commands = pytest {env:PYTESTARGS:} {posargs}