apache-ignite
361 строка · 13.9 Кб
1# Licensed to the Apache Software Foundation (ASF) under one or more
2# contributor license agreements. See the NOTICE file distributed with
3# this work for additional information regarding copyright ownership.
4# The ASF licenses this file to You under the Apache License, Version 2.0
5# (the "License"); you may not use this file except in compliance with
6# the License. You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17This module contains Spec classes that describes config and command line to start Ignite services
18"""
19
20import base6421import importlib22import json23import os24import subprocess25from abc import ABCMeta, abstractmethod26import re27from copy import deepcopy28from itertools import chain29
30from ignitetest.services.utils import IgniteServiceType31from ignitetest.services.utils.config_template import IgniteClientConfigTemplate, IgniteServerConfigTemplate, \32IgniteLoggerConfigTemplate, IgniteThinClientConfigTemplate33from ignitetest.services.utils.jvm_utils import create_jvm_settings, merge_jvm_settings34from ignitetest.services.utils.path import get_home_dir, IgnitePathAware35from ignitetest.services.utils.ssl.ssl_params import is_ssl_enabled36from ignitetest.services.utils.metrics.metrics import is_opencensus_metrics_enabled, configure_opencensus_metrics,\37is_jmx_metrics_enabled, configure_jmx_metrics38from ignitetest.services.utils.jmx_remote.jmx_remote_params import get_jmx_remote_params39from ignitetest.utils.ignite_test import JFR_ENABLED40from ignitetest.utils.version import DEV_BRANCH41
42SHARED_PREPARED_FILE = ".ignite_prepared"43
44
45def resolve_spec(service, **kwargs):46"""47Resolve Spec classes for IgniteService and IgniteApplicationService
48"""
49
50def _resolve_spec(name, default):51if name in service.context.globals:52fqdn = service.context.globals[name]53(module, clazz) = fqdn.rsplit('.', 1)54module = importlib.import_module(module)55return getattr(module, clazz)56return default57
58def is_impl(impl):59classes = map(lambda s: s.__name__, service.__class__.mro())60impl_filter = list(filter(lambda c: c == impl, classes))61return len(impl_filter) > 062
63if is_impl("IgniteService"):64return _resolve_spec("NodeSpec", IgniteNodeSpec)(service=service, **kwargs)65
66if is_impl("IgniteApplicationService"):67return _resolve_spec("AppSpec", IgniteApplicationSpec)(service=service, **kwargs)68
69raise Exception("There is no specification for class %s" % type(service))70
71
72def envs_to_exports(envs):73"""74:return: line with exports env variables: export A=B; export C=D;
75"""
76exports = ["export %s=%s" % (key, envs[key]) for key in envs]77return "; ".join(exports) + ";"78
79
80class IgniteSpec(metaclass=ABCMeta):81"""82This class is a basic Spec
83"""
84
85def __init__(self, service, jvm_opts=None, merge_with_default=True):86"""87:param service: Service
88:param jvm_opts: If passed will be added with higher priority to or overwrite completely the default options
89depending on the merge_with_default. Either string or list of strings is allowed.
90:param merge_with_default: If False jvm_opts will overide the default options completely. None of the
91default options will be applied.
92"""
93self.service = service94self.jvm_opts = merge_jvm_settings(self.__get_default_jvm_opts() if merge_with_default else [],95jvm_opts if jvm_opts else [])96
97def __get_default_jvm_opts(self):98"""99Return a set of default JVM options.
100"""
101default_jvm_opts = create_jvm_settings(gc_dump_path=os.path.join(self.service.log_dir, "gc.log"),102oom_path=os.path.join(self.service.log_dir, "out_of_mem.hprof"),103vm_error_path=os.path.join(self.service.log_dir, "hs_err_pid%p.log"))104
105default_jvm_opts = merge_jvm_settings(106default_jvm_opts, ["-DIGNITE_SUCCESS_FILE=" + os.path.join(self.service.persistent_root, "success_file"),107"-Dlog4j.configDebug=true"])108
109default_jvm_opts = merge_jvm_settings(110default_jvm_opts, ["-Dlog4j.configurationFile=file:" + self.service.log_config_file,111"-DappId=ignite"])112
113if self.service.context.globals.get(JFR_ENABLED, False):114default_jvm_opts = merge_jvm_settings(default_jvm_opts,115["-XX:+UnlockCommercialFeatures",116"-XX:+FlightRecorder",117"-XX:StartFlightRecording=dumponexit=true," +118f"filename={self.service.jfr_dir}/recording.jfr"])119
120jmx_remote_params = get_jmx_remote_params(self.service.context.globals)121if jmx_remote_params.enabled:122default_jvm_opts = merge_jvm_settings(default_jvm_opts,123["-Dcom.sun.management.jmxremote",124"-Dcom.sun.management.jmxremote.port=%d" % jmx_remote_params.port,125"-Dcom.sun.management.jmxremote.local.only=false",126"-Dcom.sun.management.jmxremote.authenticate=false",127"-Dcom.sun.management.jmxremote.ssl=false"])128
129return default_jvm_opts130
131def config_templates(self):132"""133:return: config that service will use to start on a node
134"""
135config_templates = [(IgnitePathAware.IGNITE_LOG_CONFIG_NAME, IgniteLoggerConfigTemplate())]136
137if self.service.config.service_type == IgniteServiceType.NODE:138config_templates.append((IgnitePathAware.IGNITE_CONFIG_NAME,139IgniteClientConfigTemplate() if self.service.config.client_mode140else IgniteServerConfigTemplate()))141
142if self.service.config.service_type == IgniteServiceType.THIN_CLIENT:143config_templates.append((IgnitePathAware.IGNITE_THIN_CLIENT_CONFIG_NAME, IgniteThinClientConfigTemplate()))144
145return config_templates146
147def extend_config(self, config):148"""149Extend config with custom variables
150"""
151if config.service_type == IgniteServiceType.NODE:152if is_opencensus_metrics_enabled(self.service):153config = configure_opencensus_metrics(config, self.service.context.globals, self)154
155if is_jmx_metrics_enabled(self.service):156config = configure_jmx_metrics(config)157
158if (is_opencensus_metrics_enabled(self.service) or159is_jmx_metrics_enabled(self.service)):160
161ignite_instance_name = self._test_id162
163if config.ignite_instance_name:164ignite_instance_name = ignite_instance_name[:250 - 2 - len(config.ignite_instance_name)] +\165"--" + config.ignite_instance_name166
167config = config._replace(ignite_instance_name=ignite_instance_name)168
169config = config.prepare_ssl(self.service.globals, self.service.shared_root)170
171return config172
173@property174def _test_id(self):175return re.sub("^[0-9A-Fa-f]+@ignitetest\\.tests\\.", "", self.service.context.test_name).replace("=", ".")[:250]176
177def __home(self, product=None):178"""179Get home directory for current spec.
180"""
181product = product if product else self.service.product182return get_home_dir(self.service.install_root, product)183
184@staticmethod185def __get_module_libs(project_dir, module_name, is_dev):186"""187Get absolute paths to be added to classpath for the specified module.
188"""
189if is_dev:190module_libs = [191os.path.join("modules", module_name, "target"),192os.path.join("modules", module_name, "target", "libs")193]194else:195module_libs = [196os.path.join("libs", "optional", "ignite-%s" % module_name)197]198
199return [os.path.join(project_dir, module_path) for module_path in module_libs]200
201def _module_libs(self, module_name):202"""203Get list of paths to be added to classpath for the passed module for current spec.
204"""
205if module_name == "ducktests":206return self.__get_module_libs(self.__home(str(DEV_BRANCH)), module_name, is_dev=True)207
208return self.__get_module_libs(self.__home(), module_name, self.service.config.version.is_dev)209
210@abstractmethod211def command(self, node):212"""213:return: string that represents command to run service on a node
214"""
215
216def modules(self):217"""218:return: modules set.
219"""
220if self.service.modules:221modules = deepcopy(self.service.modules)222else:223modules = []224
225modules.append("log4j2")226modules.append("ducktests")227
228if is_opencensus_metrics_enabled(self.service):229modules.append("opencensus")230
231return modules232
233def libs(self):234"""235:return: list of paths for all modules to be added to classpath
236"""
237flat_libs_list = chain(*[self._module_libs(module) for module in self.modules()])238
239return list(map(lambda lib: os.path.join(lib, "*"), flat_libs_list))240
241def envs(self):242"""243:return: environment set.
244"""
245return {246'EXCLUDE_TEST_CLASSES': 'true',247'IGNITE_LOG_DIR': self.service.log_dir,248'USER_LIBS': ":".join(self.libs())249}250
251def config_file_path(self):252"""253:return: path to project configuration file
254"""
255return self.service.config_file256
257def is_prepare_shared_files(self, local_dir):258"""259:return True if we have something to prepare.
260"""
261if not is_ssl_enabled(self.service.context.globals) and \262not (self.service.config.service_type == IgniteServiceType.NODE and self.service.config.ssl_params):263self.service.logger.debug("Ssl disabled. Nothing to generate.")264return False265
266if os.path.isfile(os.path.join(local_dir, SHARED_PREPARED_FILE)):267self.service.logger.debug("Local shared dir already prepared. Exiting. " + local_dir)268return False269
270return True271
272def prepare_shared_files(self, local_dir):273"""274Prepare files that should be copied on all nodes.
275"""
276self.service.logger.debug("Local shared dir not exists. Creating. " + local_dir)277try:278os.mkdir(local_dir)279except FileExistsError:280self.service.logger.debug("Shared dir already exists, ignoring and continue." + local_dir)281
282script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "certs")283
284self._runcmd(f"cp {script_dir}/* {local_dir}")285self._runcmd(f"chmod a+x {local_dir}/*.sh")286self._runcmd(f"{local_dir}/mkcerts.sh")287
288def _jvm_opts(self):289"""290:return: line with extra JVM params for ignite.sh script: -J-Dparam=value -J-ea
291"""
292opts = ["-J%s" % o for o in self.jvm_opts]293return " ".join(opts)294
295def _runcmd(self, cmd):296self.service.logger.debug(cmd)297proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)298stdout, _ = proc.communicate()299
300if proc.returncode != 0:301raise RuntimeError("Command '%s' returned non-zero exit status %d: %s" % (cmd, proc.returncode, stdout))302
303
304class IgniteNodeSpec(IgniteSpec):305"""306Spec to run ignite node
307"""
308
309def command(self, node):310cmd = "%s %s %s %s 2>&1 | tee -a %s &" % \311(envs_to_exports(self.envs()),312self.service.script("ignite.sh"),313self._jvm_opts(),314self.config_file_path(),315os.path.join(self.service.log_dir, "console.log"))316
317return cmd318
319
320class IgniteApplicationSpec(IgniteSpec):321"""322Spec to run ignite application
323"""
324def __init__(self, service, jvm_opts=None, merge_with_default=True):325super().__init__(326service,327merge_jvm_settings(self.__get_default_jvm_opts() if merge_with_default else [],328jvm_opts if jvm_opts else []),329merge_with_default)330
331def __get_default_jvm_opts(self):332return [333"-DIGNITE_NO_SHUTDOWN_HOOK=true", # allows performing operations on app termination.334"-Xmx1G",335"-ea",336"-DIGNITE_ALLOW_ATOMIC_OPS_IN_TX=false"337]338
339def command(self, node):340args = [341str(self.service.config.service_type.name),342self.service.java_class_name,343self.config_file_path(),344str(base64.b64encode(json.dumps(self.service.params).encode('utf-8')), 'utf-8')345]346
347cmd = "%s %s %s %s 2>&1 | tee -a %s &" % \348(envs_to_exports(self.envs()),349self.service.script("ignite.sh"),350self._jvm_opts(),351",".join(args),352os.path.join(self.service.log_dir, "console.log"))353
354return cmd355
356def config_file_path(self):357return self.service.config_file if self.service.config.service_type == IgniteServiceType.NODE \358else self.service.thin_client_config_file359
360def envs(self):361return {**super().envs(), **{"MAIN_CLASS": self.service.main_java_class}}362