8e7d469595
Dict wrapper is used for string format method using postponed keys resolving. With this class, format will not fail in case if key not found, and leaves the unknown key in the string 'as is'. This behaviour is required as a workaround for strings with bash-like variables like ${SOMEVAR} to not fail the format because of 'unknown key: SOMEVAR'. Returning the 'format' instead of 'replace' is required to use the escaping of the '{{' '}}' in the string to get '{' '}'. Change-Id: I1c2bbb083a544eb366c7b00eb1002cc4da652c33
446 lines
14 KiB
Python
446 lines
14 KiB
Python
# Copyright 2013 - 2016 Mirantis, Inc.
|
|
#
|
|
# 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 functools
|
|
import os
|
|
import signal
|
|
import socket
|
|
import string
|
|
import time
|
|
import warnings
|
|
# noinspection PyPep8Naming
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from dateutil import tz
|
|
import six
|
|
# pylint: disable=import-error
|
|
# noinspection PyUnresolvedReferences
|
|
from six.moves import http_client
|
|
# noinspection PyUnresolvedReferences
|
|
from six.moves import xmlrpc_client
|
|
# pylint: enable=import-error
|
|
|
|
from devops import error
|
|
from devops.helpers import ssh_client
|
|
from devops.helpers import subprocess_runner
|
|
from devops import logger
|
|
from devops import settings
|
|
|
|
|
|
def get_free_port():
|
|
for port in range(32000, 32100):
|
|
if not tcp_ping('localhost', port):
|
|
return port
|
|
raise error.DevopsError('No free ports available')
|
|
|
|
|
|
def icmp_ping(host, timeout=1):
|
|
"""Run ICMP ping
|
|
|
|
returns True if host is pingable
|
|
False - otherwise.
|
|
"""
|
|
result = subprocess_runner.Subprocess.execute(
|
|
"ping -c 1 -W '{timeout:d}' '{host:s}'".format(
|
|
host=host, timeout=timeout))
|
|
return result.exit_code == 0
|
|
|
|
|
|
def tcp_ping_(host, port, timeout=None):
|
|
s = socket.socket()
|
|
if timeout:
|
|
s.settimeout(timeout)
|
|
s.connect((str(host), int(port)))
|
|
s.close()
|
|
|
|
|
|
def tcp_ping(host, port, timeout=None):
|
|
"""Run TCP ping
|
|
|
|
returns True if TCP connection to specified host and port
|
|
can be established
|
|
False - otherwise.
|
|
"""
|
|
try:
|
|
tcp_ping_(host, port, timeout)
|
|
except socket.error:
|
|
return False
|
|
return True
|
|
|
|
|
|
class RunLimit(object):
|
|
def __init__(self, timeout=60, timeout_msg='Timeout'):
|
|
self.seconds = int(timeout)
|
|
self.error_message = timeout_msg
|
|
logger.debug("RunLimit.__init__(timeout={0}, timeout_msg='{1}'"
|
|
.format(timeout, timeout_msg))
|
|
|
|
def handle_timeout(self, signum, frame):
|
|
logger.debug("RunLimit.handle_timeout reached!")
|
|
raise error.TimeoutError(self.error_message.format(spent=self.seconds))
|
|
|
|
def __enter__(self):
|
|
signal.signal(signal.SIGALRM, self.handle_timeout)
|
|
signal.alarm(self.seconds)
|
|
logger.debug("RunLimit.__enter__(seconds={0}".format(self.seconds))
|
|
|
|
def __exit__(self, exc_type, value, traceback):
|
|
time_remained = signal.alarm(0)
|
|
logger.debug("RunLimit.__exit__ , remained '{0}' sec"
|
|
.format(time_remained))
|
|
|
|
|
|
def _check_wait_args(predicate,
|
|
predicate_args,
|
|
predicate_kwargs,
|
|
interval,
|
|
timeout):
|
|
|
|
if not callable(predicate):
|
|
raise TypeError("Not callable raising_predicate has been posted: '{0}'"
|
|
.format(predicate))
|
|
if not isinstance(predicate_args, (list, tuple)):
|
|
raise TypeError("Incorrect predicate_args type for '{0}', should be "
|
|
"list or tuple, got '{1}'"
|
|
.format(predicate, type(predicate_args)))
|
|
if not isinstance(predicate_kwargs, dict):
|
|
raise TypeError("Incorrect predicate_kwargs type, should be dict, "
|
|
"got {}".format(type(predicate_kwargs)))
|
|
if interval <= 0:
|
|
raise ValueError("For '{0}(*{1}, **{2})', waiting interval '{3}'sec is"
|
|
" wrong".format(predicate,
|
|
predicate_args,
|
|
predicate_kwargs,
|
|
interval))
|
|
if timeout <= 0:
|
|
raise ValueError("For '{0}(*{1}, **{2})', timeout '{3}'sec is "
|
|
"wrong".format(predicate,
|
|
predicate_args,
|
|
predicate_kwargs,
|
|
timeout))
|
|
|
|
|
|
def wait(predicate, interval=5, timeout=60, timeout_msg="Waiting timed out",
|
|
predicate_args=None, predicate_kwargs=None):
|
|
"""Wait until predicate will become True.
|
|
|
|
Options:
|
|
|
|
:param interval: - seconds between checks.
|
|
:param timeout: - raise TimeoutError if predicate won't become True after
|
|
this amount of seconds.
|
|
:param timeout_msg: - text of the TimeoutError
|
|
:param predicate_args: - positional arguments for given predicate wrapped
|
|
in list or tuple
|
|
:param predicate_kwargs: - dict with named arguments for the predicate
|
|
|
|
"""
|
|
predicate_args = predicate_args or []
|
|
predicate_kwargs = predicate_kwargs or {}
|
|
_check_wait_args(predicate, predicate_args, predicate_kwargs,
|
|
interval, timeout)
|
|
msg = (
|
|
"{msg}\nWaited for pass {cmd}: {spent} seconds."
|
|
"".format(
|
|
msg=timeout_msg,
|
|
cmd=repr(predicate),
|
|
spent="{spent:0.3f}"
|
|
))
|
|
|
|
start_time = time.time()
|
|
with RunLimit(timeout, msg):
|
|
while True:
|
|
result = predicate(*predicate_args, **predicate_kwargs)
|
|
if result:
|
|
logger.debug("wait() completed with result='{0}'"
|
|
.format(result))
|
|
return result
|
|
|
|
if start_time + timeout < time.time():
|
|
err_msg = msg.format(spent=time.time() - start_time)
|
|
logger.error(err_msg)
|
|
raise error.TimeoutError(err_msg)
|
|
|
|
time.sleep(interval)
|
|
|
|
|
|
def wait_pass(raising_predicate, expected=Exception,
|
|
interval=5, timeout=60, timeout_msg="Waiting timed out",
|
|
predicate_args=None, predicate_kwargs=None):
|
|
"""Wait for successful return from predicate ignoring expected exception
|
|
|
|
Options:
|
|
|
|
:param interval: - seconds between checks.
|
|
:param timeout: - raise TimeoutError if predicate still throwing expected
|
|
exception after this amount of seconds.
|
|
:param timeout_msg: - text of the TimeoutError
|
|
:param predicate_args: - positional arguments for given predicate wrapped
|
|
in list or tuple
|
|
:param predicate_kwargs: - dict with named arguments for the predicate
|
|
:param expected_exc: Exception that can be ignored while waiting (its
|
|
possible to pass several using list/tuple
|
|
|
|
"""
|
|
|
|
predicate_args = predicate_args or []
|
|
predicate_kwargs = predicate_kwargs or {}
|
|
_check_wait_args(raising_predicate, predicate_args, predicate_kwargs,
|
|
interval, timeout)
|
|
msg = (
|
|
"{msg}\nWaited for pass {cmd}: {spent} seconds."
|
|
"".format(
|
|
msg=timeout_msg,
|
|
cmd=repr(raising_predicate),
|
|
spent="{spent:0.3f}"
|
|
))
|
|
|
|
start_time = time.time()
|
|
with RunLimit(timeout, msg):
|
|
while True:
|
|
try:
|
|
result = raising_predicate(*predicate_args, **predicate_kwargs)
|
|
logger.debug("wait_pass() completed with result='{0}'"
|
|
.format(result))
|
|
return result
|
|
except expected as e:
|
|
if start_time + timeout < time.time():
|
|
err_msg = msg.format(spent=time.time() - start_time)
|
|
logger.error(err_msg)
|
|
raise error.TimeoutError(err_msg)
|
|
|
|
logger.debug("Got expected exception {!r}, continue".format(e))
|
|
time.sleep(interval)
|
|
|
|
|
|
def wait_tcp(host, port, timeout, timeout_msg="Waiting timed out"):
|
|
wait(tcp_ping, timeout=timeout, timeout_msg=timeout_msg,
|
|
predicate_kwargs={'host': host, 'port': port})
|
|
|
|
|
|
def wait_ssh_cmd(
|
|
host,
|
|
port,
|
|
check_cmd,
|
|
username=settings.SSH_CREDENTIALS['login'],
|
|
password=settings.SSH_CREDENTIALS['password'],
|
|
timeout=0):
|
|
ssh = ssh_client.SSHClient(host=host, port=port,
|
|
auth=ssh_client.SSHAuth(
|
|
username=username,
|
|
password=password))
|
|
wait(lambda: not ssh.execute(check_cmd)['exit_code'],
|
|
timeout=timeout)
|
|
|
|
|
|
def http(host='localhost', port=80, method='GET', url='/', waited_code=200):
|
|
try:
|
|
conn = http_client.HTTPConnection(str(host), int(port))
|
|
conn.request(method, url)
|
|
res = conn.getresponse()
|
|
|
|
return res.status == waited_code
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def get_private_keys(env):
|
|
msg = (
|
|
'get_private_keys has been deprecated in favor of '
|
|
'DevopsEnvironment.get_private_keys')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops import client
|
|
denv = client.DevopsClient().get_env(env.name)
|
|
return denv.get_private_keys()
|
|
|
|
|
|
def get_admin_remote(
|
|
env,
|
|
login=settings.SSH_CREDENTIALS['login'],
|
|
password=settings.SSH_CREDENTIALS['password']):
|
|
msg = (
|
|
'get_admin_remote has been deprecated in favor of '
|
|
'DevopsEnvironment.get_admin_remote')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops import client
|
|
denv = client.DevopsClient().get_env(env.name)
|
|
return denv.get_admin_remote(login=login, password=password)
|
|
|
|
|
|
def get_node_remote(
|
|
env,
|
|
node_name,
|
|
login=settings.SSH_SLAVE_CREDENTIALS['login'],
|
|
password=settings.SSH_SLAVE_CREDENTIALS['password']):
|
|
msg = (
|
|
'get_node_remote has been deprecated in favor of '
|
|
'DevopsEnvironment.get_node_remote')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops.client import DevopsClient
|
|
denv = DevopsClient().get_env(env.name)
|
|
return denv.get_node_remote(
|
|
node_name=node_name, login=login, password=password)
|
|
|
|
|
|
def get_admin_ip(env):
|
|
msg = (
|
|
'get_admin_ip has been deprecated in favor of '
|
|
'DevopsEnvironment.get_admin_ip')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops import client
|
|
denv = client.DevopsClient().get_env(env.name)
|
|
return denv.get_admin_ip()
|
|
|
|
|
|
def get_slave_ip(env, node_mac_address):
|
|
msg = (
|
|
'get_slave_ip has been deprecated in favor of '
|
|
'DevopsEnvironment.get_node_ip')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops import client
|
|
from devops.client import nailgun
|
|
denv = client.DevopsClient().get_env(env.name)
|
|
ng_client = nailgun.NailgunClient(ip=denv.get_admin_ip())
|
|
return ng_client.get_slave_ip_by_mac(node_mac_address)
|
|
|
|
|
|
def xmlrpctoken(uri, login, password):
|
|
server = xmlrpc_client.Server(uri)
|
|
try:
|
|
return server.login(login, password)
|
|
except Exception:
|
|
raise error.AuthenticationError("Error occurred while login process")
|
|
|
|
|
|
def xmlrpcmethod(uri, method):
|
|
server = xmlrpc_client.Server(uri)
|
|
try:
|
|
return getattr(server, method)
|
|
except Exception:
|
|
raise AttributeError("Error occurred while getting server method")
|
|
|
|
|
|
def generate_mac():
|
|
return "64:{0:02x}:{1:02x}:{2:02x}:{3:02x}:{4:02x}".format(
|
|
*bytearray(os.urandom(5)))
|
|
|
|
|
|
def get_file_size(path):
|
|
"""Get size of file-like object
|
|
|
|
:type path: str
|
|
:rtype : int
|
|
"""
|
|
|
|
return os.stat(path).st_size
|
|
|
|
|
|
def xml_tostring(tree):
|
|
"""Converts ElementTree object to string
|
|
|
|
:type tree: ElementTree
|
|
:rtype: str
|
|
"""
|
|
if six.PY2:
|
|
return ET.tostring(tree)
|
|
else:
|
|
return ET.tostring(tree, encoding='unicode')
|
|
|
|
|
|
def deepgetattr(obj, attr, default=None, splitter='.', do_raise=False):
|
|
"""Recurses through an attribute chain to get the ultimate value.
|
|
|
|
:type obj: object
|
|
:param obj: object instance to get attribute from
|
|
:type attr: str
|
|
:param attr: attributes joined by some symbol. e.g. 'a.b.c.d'
|
|
:type default: any
|
|
:param default: default value (returned only in case of
|
|
AttributeError)
|
|
:type splitter: str
|
|
:param splitter: one or more symbols to be used to split attr
|
|
parameter
|
|
:type do_raise: bool
|
|
:param do_raise: if True then instead of returning default value
|
|
AttributeError will be raised
|
|
|
|
"""
|
|
try:
|
|
return functools.reduce(getattr, attr.split(splitter), obj)
|
|
except AttributeError:
|
|
if do_raise:
|
|
raise
|
|
return default
|
|
|
|
|
|
def underscored(*args):
|
|
"""Joins multiple strings using uderscore symbol.
|
|
|
|
Skips empty strings.
|
|
"""
|
|
return '_'.join(filter(bool, list(args)))
|
|
|
|
|
|
def get_nodes(admin_ip):
|
|
msg = ('get_nodes has been deprecated in favor of '
|
|
'NailgunClient.get_nodes_json')
|
|
logger.warning(msg)
|
|
warnings.warn(msg, DeprecationWarning)
|
|
|
|
from devops.client import nailgun
|
|
ng_client = nailgun.NailgunClient(ip=admin_ip)
|
|
return ng_client.get_nodes_json()
|
|
|
|
|
|
def utc_to_local(t):
|
|
"""Converts UTC datetime to local
|
|
|
|
:type t: datetime.datetime
|
|
:rtype : datetime.datetime
|
|
"""
|
|
# set utc tzinfo
|
|
t = t.replace(tzinfo=tz.tzutc())
|
|
# convert to local timezone
|
|
return t.astimezone(tz.tzlocal())
|
|
|
|
|
|
def format_data(data_content, data_context):
|
|
"""Dict wrapper.
|
|
|
|
Dict wrapper that returns key name
|
|
in case of key missing in the dictionary
|
|
"""
|
|
|
|
class temp_dict(dict):
|
|
def __init__(self, kw):
|
|
self.__dict = kw
|
|
|
|
def __getitem__(self, key):
|
|
return self.__dict.get(key, '{' + str(key) + '}')
|
|
|
|
return string.Formatter().vformat(data_content, [], temp_dict(
|
|
data_context))
|