Files
impala/tests/util/cluster_controller.py
Casey Ching 0ee478d1ce Python: Improve usability of the ClusterController class
This is general clean up in prep for use with the stress test.

Changes:
  1) Failed commands and failure to connect now raise exceptions.
     Previously run_cmd() was not guaranteed to do anything at all in
     remote mode.
  2) Fix scope of 'hosts' which was at the class level but was modified
     by instance level functions which makes no sense since different
     instances could clash with each other.
  3) Remove uses of opaque *args and **kwargs instead of named args. The
     generic forms should be avoided since they impair readability.
  4) Stop trying to get the cluster hosts from an environment variable
     unconditionally upon construction.
  5) Remove 'local' member variable, it's not needed and allowing 'local'
     to be set to False when no 'hosts' are not set makes no sense.
  6) Simplify and remove unneeded methods and arguments.

Change-Id: Id90bd3b640f2681bb7e82a5e6d5e49ed8c5a7b98
Reviewed-on: http://gerrit.cloudera.org:8080/514
Reviewed-by: Casey Ching <casey@cloudera.com>
Tested-by: Internal Jenkins
2015-07-22 00:44:45 +00:00

102 lines
3.8 KiB
Python

# Copyright (c) 2012 Cloudera, 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.
import fabric.decorators
import logging
import os
from contextlib import contextmanager
from fabric.context_managers import hide, settings
from fabric.operations import local, run, sudo
from fabric.tasks import execute
from textwrap import dedent
LOG = logging.getLogger('cluster_controller')
class ClusterController(object):
"""A convenience wrapper around fabric."""
def __init__(self, ssh_user=os.environ.get('FABRIC_SSH_USER'),
ssh_key_path=os.environ.get('FABRIC_SSH_KEY'), host_names=(),
host_names_path=os.environ.get('FABRIC_HOST_FILE'), ssh_timeout_secs=60,
ssh_port=22):
"""If no hosts are given, command execution will be done locally.
If the FABRIC_HOST_FILE environment variable is used, it should be a path to a
text file with a list of fully qualified host names each terminated by an EOL.
"""
self.ssh_user = ssh_user
self.ssh_key_path = ssh_key_path
if host_names:
self.hosts = host_names
elif host_names_path:
with open(host_names_path, 'r') as host_file:
self.hosts = [h.strip('\n') for h in host_file.readlines()]
self.ssh_port = ssh_port
self.ssh_timeout_secs = ssh_timeout_secs
@contextmanager
def _cmd_settings(self, _use_deprecated_mode):
settings_args = [hide("running", "stdout")]
settings_kwargs = {
"abort_on_prompts": True,
"connection_attempts": 10,
"disable_known_hosts": True,
"keepalive": True,
"key_filename": self.ssh_key_path,
"parallel": True,
"timeout": self.ssh_timeout_secs,
"use_ssh_config": True,
"user": self.ssh_user}
if _use_deprecated_mode:
settings_kwargs["warn_only"] = True
else:
settings_args += [hide("warnings", "stderr")]
settings_kwargs["abort_exception"] = Exception
with settings(*settings_args, **settings_kwargs):
yield
def run_cmd(self, cmd, cmd_prefix="set -euo pipefail", hosts=(),
_use_deprecated_mode=False):
"""Runs the given command and blocks until it completes then returns a dictionary
containing the command output keyed by host name. 'cmd_prefix' will be prepended
to the cmd if it is set.
If '_use_deprecated_mode' is enabled:
1) Sudo will be used. (Deprecated because it breaks the remote/local
transparency.)
2) Command failures will generate warning but not raise exceptions. (Deprecated
since runtime behavior is not reliable.)
3) The 'cmd_prefix' argument will be ignored.
4) Additional runtime information about command execution will be sent to stdout.
"""
with self._cmd_settings(_use_deprecated_mode):
if not _use_deprecated_mode and cmd_prefix:
cmd = cmd_prefix + "\n" + cmd
cmd = dedent(cmd)
cmd_hosts = hosts or self.hosts
if cmd_hosts:
@fabric.decorators.hosts(cmd_hosts)
def task():
if _use_deprecated_mode:
return sudo(cmd)
else:
return run(cmd)
return execute(task)
else:
return {"localhost": local(cmd)}
def deprecated_run_cmd(self, cmd, hosts=()):
"""No new code should use this."""
return self.run_cmd(cmd, cmd_prefix=None, hosts=hosts, _use_deprecated_mode=True)