-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ignite ducktape control sh #8127
Changes from all commits
cdf7516
ab91e89
c6c43e0
a28fcbf
c856aab
b9f397e
b90cdad
30c032e
0352e18
3d39d83
084a91b
0f4508c
55f7102
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,3 +87,4 @@ packages | |
*.pyc | ||
/tests/venv | ||
modules/ducktests/tests/docker/build/** | ||
modules/ducktests/tests/.tox |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# 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 control utility wrapper. | ||
""" | ||
import random | ||
import re | ||
from collections import namedtuple | ||
|
||
from ducktape.cluster.remoteaccount import RemoteCommandError | ||
|
||
|
||
class ControlUtility: | ||
""" | ||
Control utility (control.sh) wrapper. | ||
""" | ||
BASE_COMMAND = "control.sh" | ||
|
||
def __init__(self, cluster, text_context): | ||
self._cluster = cluster | ||
self.logger = text_context.logger | ||
|
||
def baseline(self): | ||
""" | ||
:return Baseline nodes. | ||
""" | ||
return self.cluster_state().baseline | ||
|
||
def cluster_state(self): | ||
""" | ||
:return: Cluster state. | ||
""" | ||
output = self.__run("--baseline") | ||
|
||
return self.__parse_cluster_state(output) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use 'result' instead of 'output', as it's used in other methods. Or vice versa |
||
|
||
def set_baseline(self, baseline): | ||
""" | ||
:param baseline: Baseline nodes or topology version to set as baseline. | ||
""" | ||
if isinstance(baseline, int): | ||
result = self.__run("--baseline version %d --yes" % baseline) | ||
else: | ||
result = self.__run("--baseline set %s --yes" % | ||
",".join([node.account.externally_routable_ip for node in baseline])) | ||
|
||
return self.__parse_cluster_state(result) | ||
|
||
def add_to_baseline(self, nodes): | ||
""" | ||
:param nodes: Nodes that should be added to baseline. | ||
""" | ||
result = self.__run("--baseline add %s --yes" % | ||
",".join([node.account.externally_routable_ip for node in nodes])) | ||
|
||
return self.__parse_cluster_state(result) | ||
|
||
def remove_from_baseline(self, nodes): | ||
""" | ||
:param nodes: Nodes that should be removed to baseline. | ||
""" | ||
result = self.__run("--baseline remove %s --yes" % | ||
",".join([node.account.externally_routable_ip for node in nodes])) | ||
|
||
return self.__parse_cluster_state(result) | ||
|
||
def disable_baseline_auto_adjust(self): | ||
""" | ||
Disable baseline auto adjust. | ||
""" | ||
return self.__run("--baseline auto_adjust disable --yes") | ||
|
||
def enable_baseline_auto_adjust(self, timeout=None): | ||
""" | ||
Enable baseline auto adjust. | ||
:param timeout: Auto adjust timeout in millis. | ||
""" | ||
timeout_str = "timeout %d" % timeout if timeout else "" | ||
return self.__run("--baseline auto_adjust enable %s --yes" % timeout_str) | ||
|
||
def activate(self): | ||
""" | ||
Activate cluster. | ||
""" | ||
return self.__run("--activate --yes") | ||
|
||
def deactivate(self): | ||
""" | ||
Deactivate cluster. | ||
""" | ||
return self.__run("--deactivate --yes") | ||
|
||
@staticmethod | ||
def __parse_cluster_state(output): | ||
state_pattern = re.compile("Cluster state: ([^\\s]+)") | ||
topology_pattern = re.compile("Current topology version: (\\d+)") | ||
baseline_pattern = re.compile("Consistent(Id|ID)=([^\\s]+),\\sS(tate|TATE)=([^\\s]+),?(\\sOrder=(\\d+))?") | ||
|
||
match = state_pattern.search(output) | ||
state = match.group(1) if match else None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think, is it better to use named group instead of indices? |
||
|
||
match = topology_pattern.search(output) | ||
topology = int(match.group(1)) if match else None | ||
|
||
baseline = [BaselineNode(consistent_id=m[1], state=m[3], order=int(m[5]) if m[5] else None) | ||
for m in baseline_pattern.findall(output)] | ||
|
||
return ClusterState(state=state, topology_version=topology, baseline=baseline) | ||
|
||
def __run(self, cmd): | ||
node = random.choice(self.__alives()) | ||
|
||
self.logger.debug("Run command %s on node %s", cmd, node.name) | ||
|
||
raw_output = node.account.ssh_capture(self.__form_cmd(node, cmd), allow_fail=True) | ||
code, output = self.__parse_output(raw_output) | ||
|
||
self.logger.debug("Output of command %s on node %s, exited with code %d, is %s", cmd, node.name, code, output) | ||
|
||
if code != 0: | ||
raise ControlUtilityError(node.account, cmd, code, output) | ||
|
||
return output | ||
|
||
def __form_cmd(self, node, cmd): | ||
return self._cluster.path.script("%s --host %s %s" % (self.BASE_COMMAND, node.account.externally_routable_ip, | ||
cmd)) | ||
|
||
@staticmethod | ||
def __parse_output(raw_output): | ||
exit_code = raw_output.channel_file.channel.recv_exit_status() | ||
output = "".join(raw_output) | ||
|
||
pattern = re.compile("Command \\[[^\\s]*\\] finished with code: (\\d+)") | ||
match = pattern.search(output) | ||
|
||
if match: | ||
return int(match.group(1)), output | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok, that it's possible to receive |
||
return exit_code, output | ||
|
||
def __alives(self): | ||
return [node for node in self._cluster.nodes if self._cluster.alive(node)] | ||
|
||
|
||
BaselineNode = namedtuple("BaselineNode", ["consistent_id", "state", "order"]) | ||
ClusterState = namedtuple("ClusterState", ["state", "topology_version", "baseline"]) | ||
|
||
|
||
class ControlUtilityError(RemoteCommandError): | ||
""" | ||
Error is raised when control utility failed. | ||
""" | ||
def __init__(self, account, cmd, exit_status, output): | ||
super(ControlUtilityError, self).__init__(account, cmd, exit_status, "".join(output)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,17 +18,19 @@ | |
""" | ||
|
||
import os | ||
from abc import abstractmethod | ||
from abc import abstractmethod, ABCMeta | ||
|
||
from ducktape.services.background_thread import BackgroundThreadService | ||
from ducktape.utils.util import wait_until | ||
from six import add_metaclass | ||
|
||
from ignitetest.services.utils.ignite_config import IgniteLoggerConfig, IgniteServerConfig, IgniteClientConfig | ||
from ignitetest.services.utils.ignite_path import IgnitePath | ||
from ignitetest.services.utils.jmx_utils import ignite_jmx_mixin | ||
from ignitetest.tests.utils.version import IgniteVersion | ||
|
||
|
||
@add_metaclass(ABCMeta) | ||
class IgniteAwareService(BackgroundThreadService): | ||
""" | ||
The base class to build services aware of Ignite. | ||
|
@@ -85,8 +87,14 @@ def init_persistent(self, node): | |
logger_config = IgniteLoggerConfig().render(work_dir=self.WORK_DIR) | ||
|
||
node.account.mkdirs(self.PERSISTENT_ROOT) | ||
node.account.create_file(self.CONFIG_FILE, self.config().render( | ||
config_dir=self.PERSISTENT_ROOT, work_dir=self.WORK_DIR, properties=self.properties)) | ||
|
||
node_config = self.config().render(config_dir=self.PERSISTENT_ROOT, | ||
work_dir=self.WORK_DIR, | ||
properties=self.properties, | ||
consistent_id=node.account.externally_routable_ip) | ||
|
||
setattr(node, "consistent_id", node.account.externally_routable_ip) | ||
node.account.create_file(self.CONFIG_FILE, node_config) | ||
node.account.create_file(self.LOG4J_CONFIG_FILE, logger_config) | ||
|
||
@abstractmethod | ||
|
@@ -156,10 +164,9 @@ def await_event(self, evt_message, timeout_sec, from_the_beginning=False, backof | |
:param backoff_sec: Number of seconds to back off between each failure to meet the condition | ||
before checking again. | ||
""" | ||
assert len(self.nodes) == 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was there a reason to have the assert? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @timoninmaxim It was implemented partially by me, assert was used to check it was used properly (applicable only for single-node services) |
||
|
||
self.await_event_on_node(evt_message, self.nodes[0], timeout_sec, from_the_beginning=from_the_beginning, | ||
backoff_sec=backoff_sec) | ||
for node in self.nodes: | ||
self.await_event_on_node(evt_message, node, timeout_sec, from_the_beginning=from_the_beginning, | ||
backoff_sec=backoff_sec) | ||
|
||
def execute(self, command): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we really need EXCLUDE_MODULES?