|
Packit Service |
a04d08 |
# This file is part of cloud-init. See LICENSE file for license information.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
"""Utilities for re-use across integration tests."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
import base64
|
|
Packit Service |
a04d08 |
import copy
|
|
Packit Service |
a04d08 |
import glob
|
|
Packit Service |
a04d08 |
import os
|
|
Packit Service |
a04d08 |
import random
|
|
Packit Service |
a04d08 |
import shlex
|
|
Packit Service |
a04d08 |
import shutil
|
|
Packit Service |
a04d08 |
import string
|
|
Packit Service |
a04d08 |
import subprocess
|
|
Packit Service |
a04d08 |
import tempfile
|
|
Packit Service |
a04d08 |
import yaml
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
from cloudinit import util as c_util
|
|
Packit Service |
a04d08 |
from tests.cloud_tests import LOG
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
OS_FAMILY_MAPPING = {
|
|
Packit Service |
a04d08 |
'debian': ['debian', 'ubuntu'],
|
|
Packit Service |
a04d08 |
'redhat': ['centos', 'rhel', 'fedora'],
|
|
Packit Service |
a04d08 |
'gentoo': ['gentoo'],
|
|
Packit Service |
a04d08 |
'freebsd': ['freebsd'],
|
|
Packit Service |
a04d08 |
'suse': ['sles'],
|
|
Packit Service |
a04d08 |
'arch': ['arch'],
|
|
Packit Service |
a04d08 |
}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def list_test_data(data_dir):
|
|
Packit Service |
a04d08 |
"""Find all tests with test data available in data_dir.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param data_dir: should contain <platforms>/<os_name>/<testnames>/<data>
|
|
Packit Service |
a04d08 |
@return_value: {<platform>: {<os_name>: [<testname>]}}
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
if not os.path.isdir(data_dir):
|
|
Packit Service |
a04d08 |
raise ValueError("bad data dir")
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
res = {}
|
|
Packit Service |
a04d08 |
for platform in os.listdir(data_dir):
|
|
Packit Service |
a04d08 |
if not os.path.isdir(os.path.join(data_dir, platform)):
|
|
Packit Service |
a04d08 |
continue
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
res[platform] = {}
|
|
Packit Service |
a04d08 |
for os_name in os.listdir(os.path.join(data_dir, platform)):
|
|
Packit Service |
a04d08 |
res[platform][os_name] = [
|
|
Packit Service |
a04d08 |
os.path.sep.join(f.split(os.path.sep)[-2:]) for f in
|
|
Packit Service |
a04d08 |
glob.glob(os.sep.join((data_dir, platform, os_name, '*/*')))]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
LOG.debug('found test data: %s\n', res)
|
|
Packit Service |
a04d08 |
return res
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def gen_instance_name(prefix='cloud-test', image_desc=None, use_desc=None,
|
|
Packit Service |
a04d08 |
max_len=63, delim='-', max_tries=16, used_list=None,
|
|
Packit Service |
a04d08 |
valid=string.ascii_lowercase + string.digits):
|
|
Packit Service |
a04d08 |
"""Generate an unique name for a test instance.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param prefix: name prefix, defaults to cloud-test, default should be left
|
|
Packit Service |
a04d08 |
@param image_desc: short string (len <= 16) with image desc
|
|
Packit Service |
a04d08 |
@param use_desc: short string (len <= 30) with usage desc
|
|
Packit Service |
a04d08 |
@param max_len: maximum name length, defaults to 64 chars
|
|
Packit Service |
a04d08 |
@param delim: delimiter to use between tokens
|
|
Packit Service |
a04d08 |
@param max_tries: maximum tries to find a unique name before giving up
|
|
Packit Service |
a04d08 |
@param used_list: already used names, or none to not check
|
|
Packit Service |
a04d08 |
@param valid: string of valid characters for name
|
|
Packit Service |
a04d08 |
@return_value: valid, unused name, may raise StopIteration
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
unknown = 'unknown'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def join(*args):
|
|
Packit Service |
a04d08 |
"""Join args with delim."""
|
|
Packit Service |
a04d08 |
return delim.join(args)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def fill(*args):
|
|
Packit Service |
a04d08 |
"""Join name elems and fill rest with random data."""
|
|
Packit Service |
a04d08 |
name = join(*args)
|
|
Packit Service |
a04d08 |
num = max_len - len(name) - len(delim)
|
|
Packit Service |
a04d08 |
return join(name, ''.join(random.choice(valid) for _ in range(num)))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def clean(elem, max_len):
|
|
Packit Service |
a04d08 |
"""Filter bad characters out of elem and trim to length."""
|
|
Packit Service |
a04d08 |
elem = elem.lower()[:max_len] if elem else unknown
|
|
Packit Service |
a04d08 |
return ''.join(c if c in valid else delim for c in elem)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
return next(name for name in
|
|
Packit Service |
a04d08 |
(fill(prefix, clean(image_desc, 16), clean(use_desc, 30))
|
|
Packit Service |
a04d08 |
for _ in range(max_tries))
|
|
Packit Service |
a04d08 |
if not used_list or name not in used_list)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def sorted_unique(iterable, key=None, reverse=False):
|
|
Packit Service |
a04d08 |
"""Create unique sorted list.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param iterable: the data structure to sort
|
|
Packit Service |
a04d08 |
@param key: if you have a specific key
|
|
Packit Service |
a04d08 |
@param reverse: to reverse or not
|
|
Packit Service |
a04d08 |
@return_value: a sorted list of unique items in iterable
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
return sorted(set(iterable), key=key, reverse=reverse)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_os_family(os_name):
|
|
Packit Service |
a04d08 |
"""Get os family type for os_name.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param os_name: name of os
|
|
Packit Service |
a04d08 |
@return_value: family name for os_name
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
return next((k for k, v in OS_FAMILY_MAPPING.items()
|
|
Packit Service |
a04d08 |
if os_name.lower() in v), None)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def current_verbosity():
|
|
Packit Service |
a04d08 |
"""Get verbosity currently in effect from log level.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@return_value: verbosity, 0-2, 2=verbose, 0=quiet
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
return max(min(3 - int(LOG.level / 10), 2), 0)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def is_writable_dir(path):
|
|
Packit Service |
a04d08 |
"""Make sure dir is writable.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param path: path to determine if writable
|
|
Packit Service |
a04d08 |
@return_value: boolean with result
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
c_util.ensure_dir(path)
|
|
Packit Service |
a04d08 |
os.remove(tempfile.mkstemp(dir=os.path.abspath(path))[1])
|
|
Packit Service |
a04d08 |
except (IOError, OSError):
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
return True
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def is_clean_writable_dir(path):
|
|
Packit Service |
a04d08 |
"""Make sure dir is empty and writable, creating it if it does not exist.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param path: path to check
|
|
Packit Service |
a04d08 |
@return_value: True/False if successful
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
path = os.path.abspath(path)
|
|
Packit Service |
a04d08 |
if not (is_writable_dir(path) and len(os.listdir(path)) == 0):
|
|
Packit Service |
a04d08 |
return False
|
|
Packit Service |
a04d08 |
return True
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def configure_yaml():
|
|
Packit Service |
a04d08 |
"""Clean yaml."""
|
|
Packit Service |
a04d08 |
yaml.add_representer(str, (lambda dumper, data: dumper.represent_scalar(
|
|
Packit Service |
a04d08 |
'tag:yaml.org,2002:str', data, style='|' if '\n' in data else '')))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def yaml_format(data, content_type=None):
|
|
Packit Service |
a04d08 |
"""Format data as yaml.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param data: data to dump
|
|
Packit Service |
a04d08 |
@param header: if specified, add a header to the dumped data
|
|
Packit Service |
a04d08 |
@return_value: yaml string
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
configure_yaml()
|
|
Packit Service |
a04d08 |
content_type = (
|
|
Packit Service |
a04d08 |
'#{}\n'.format(content_type.strip('#\n')) if content_type else '')
|
|
Packit Service |
a04d08 |
return content_type + yaml.dump(data, indent=2, default_flow_style=False)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def yaml_dump(data, path):
|
|
Packit Service |
a04d08 |
"""Dump data to path in yaml format."""
|
|
Packit Service |
a04d08 |
c_util.write_file(os.path.abspath(path), yaml_format(data), omode='w')
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def merge_results(data, path):
|
|
Packit Service |
a04d08 |
"""Handle merging results from collect phase and verify phase."""
|
|
Packit Service |
a04d08 |
current = {}
|
|
Packit Service |
a04d08 |
if os.path.exists(path):
|
|
Packit Service |
a04d08 |
with open(path, 'r') as fp:
|
|
Packit Service |
a04d08 |
current = c_util.load_yaml(fp.read())
|
|
Packit Service |
a04d08 |
current.update(data)
|
|
Packit Service |
a04d08 |
yaml_dump(current, path)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def rel_files(basedir):
|
|
Packit Service |
a04d08 |
"""List of files under directory by relative path, not including dirs.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param basedir: directory to search
|
|
Packit Service |
a04d08 |
@return_value: list or relative paths
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
basedir = os.path.normpath(basedir)
|
|
Packit Service |
a04d08 |
return [path[len(basedir) + 1:] for path in
|
|
Packit Service |
a04d08 |
glob.glob(os.path.join(basedir, '**'), recursive=True)
|
|
Packit Service |
a04d08 |
if not os.path.isdir(path)]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def flat_tar(output, basedir, owner='root', group='root'):
|
|
Packit Service |
a04d08 |
"""Create a flat tar archive (no leading ./) from basedir.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param output: output tar file to write
|
|
Packit Service |
a04d08 |
@param basedir: base directory for archive
|
|
Packit Service |
a04d08 |
@param owner: owner of archive files
|
|
Packit Service |
a04d08 |
@param group: group archive files belong to
|
|
Packit Service |
a04d08 |
@return_value: none
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
11b429 |
c_util.subp(['tar', 'cf', output, '--owner', owner, '--group', group,
|
|
Packit Service |
11b429 |
'-C', basedir] + rel_files(basedir), capture=True)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def parse_conf_list(entries, valid=None, boolean=False):
|
|
Packit Service |
a04d08 |
"""Parse config in a list of strings in key=value format.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param entries: list of key=value strings
|
|
Packit Service |
a04d08 |
@param valid: list of valid keys in result, return None if invalid input
|
|
Packit Service |
a04d08 |
@param boolean: if true, then interpret all values as booleans
|
|
Packit Service |
a04d08 |
@return_value: dict of configuration or None if invalid
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
res = {key: value.lower() == 'true' if boolean else value
|
|
Packit Service |
a04d08 |
for key, value in (i.split('=') for i in entries)}
|
|
Packit Service |
a04d08 |
return res if not valid or all(k in valid for k in res.keys()) else None
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def update_args(args, updates, preserve_old=True):
|
|
Packit Service |
a04d08 |
"""Update cmdline arguments from a dictionary.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param args: cmdline arguments
|
|
Packit Service |
a04d08 |
@param updates: dictionary of {arg_name: new_value} mappings
|
|
Packit Service |
a04d08 |
@param preserve_old: if true, create a deep copy of args before updating
|
|
Packit Service |
a04d08 |
@return_value: updated cmdline arguments
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
args = copy.deepcopy(args) if preserve_old else args
|
|
Packit Service |
a04d08 |
if updates:
|
|
Packit Service |
a04d08 |
vars(args).update(updates)
|
|
Packit Service |
a04d08 |
return args
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def update_user_data(user_data, updates, dump_to_yaml=True):
|
|
Packit Service |
a04d08 |
"""Update user_data from dictionary.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param user_data: user data as yaml string or dict
|
|
Packit Service |
a04d08 |
@param updates: dictionary to merge with user data
|
|
Packit Service |
a04d08 |
@param dump_to_yaml: return as yaml dumped string if true
|
|
Packit Service |
a04d08 |
@return_value: updated user data, as yaml string if dump_to_yaml is true
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
user_data = (c_util.load_yaml(user_data)
|
|
Packit Service |
a04d08 |
if isinstance(user_data, str) else copy.deepcopy(user_data))
|
|
Packit Service |
a04d08 |
user_data.update(updates)
|
|
Packit Service |
a04d08 |
return (yaml_format(user_data, content_type='cloud-config')
|
|
Packit Service |
a04d08 |
if dump_to_yaml else user_data)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def shell_safe(cmd):
|
|
Packit Service |
a04d08 |
"""Produce string safe shell string.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Create a string that can be passed to:
|
|
Packit Service |
a04d08 |
set -- <string>
|
|
Packit Service |
a04d08 |
to produce the same array that cmd represents.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Internally we utilize 'getopt's ability/knowledge on how to quote
|
|
Packit Service |
a04d08 |
strings to be safe for shell. This implementation could be changed
|
|
Packit Service |
a04d08 |
to be pure python. It is just a matter of correctly escaping
|
|
Packit Service |
a04d08 |
or quoting characters like: ' " ^ & $ ; ( ) ...
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param cmd: command as a list
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
out = subprocess.check_output(
|
|
Packit Service |
a04d08 |
["getopt", "--shell", "sh", "--options", "", "--", "--"] + list(cmd))
|
|
Packit Service |
a04d08 |
# out contains ' -- <data>\n'. drop the ' -- ' and the '\n'
|
|
Packit Service |
a04d08 |
return out.decode()[4:-1]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def shell_pack(cmd):
|
|
Packit Service |
a04d08 |
"""Return a string that can shuffled through 'sh' and execute cmd.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
In Python subprocess terms:
|
|
Packit Service |
a04d08 |
check_output(cmd) == check_output(shell_pack(cmd), shell=True)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param cmd: list or string of command to pack up
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if isinstance(cmd, str):
|
|
Packit Service |
a04d08 |
cmd = [cmd]
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
cmd = list(cmd)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
stuffed = shell_safe(cmd)
|
|
Packit Service |
a04d08 |
# for whatever reason b64encode returns bytes when it is clearly
|
|
Packit Service |
a04d08 |
# representable as a string by nature of being base64 encoded.
|
|
Packit Service |
a04d08 |
b64 = base64.b64encode(stuffed.encode()).decode()
|
|
Packit Service |
a04d08 |
return 'eval set -- "$(echo %s | base64 --decode)" && exec "$@"' % b64
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def shell_quote(cmd):
|
|
Packit Service |
a04d08 |
if isinstance(cmd, (tuple, list)):
|
|
Packit Service |
a04d08 |
return ' '.join([shlex.quote(x) for x in cmd])
|
|
Packit Service |
a04d08 |
return shlex.quote(cmd)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class TargetBase(object):
|
|
Packit Service |
a04d08 |
_tmp_count = 0
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def execute(self, command, stdin=None, env=None,
|
|
Packit Service |
a04d08 |
rcs=None, description=None):
|
|
Packit Service |
a04d08 |
"""Execute command in instance, recording output, error and exit code.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Assumes functional networking and execution as root with the
|
|
Packit Service |
a04d08 |
target filesystem being available at /.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param command: the command to execute as root inside the image
|
|
Packit Service |
a04d08 |
if command is a string, then it will be executed as:
|
|
Packit Service |
a04d08 |
['sh', '-c', command]
|
|
Packit Service |
a04d08 |
@param stdin: bytes content for standard in
|
|
Packit Service |
a04d08 |
@param env: environment variables
|
|
Packit Service |
a04d08 |
@param rcs: return codes.
|
|
Packit Service |
a04d08 |
None (default): non-zero exit code will raise exception.
|
|
Packit Service |
a04d08 |
False: any is allowed (No execption raised).
|
|
Packit Service |
a04d08 |
list of int: any rc not in the list will raise exception.
|
|
Packit Service |
a04d08 |
@param description: purpose of command
|
|
Packit Service |
a04d08 |
@return_value: tuple containing stdout data, stderr data, exit code
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
if isinstance(command, str):
|
|
Packit Service |
a04d08 |
command = ['sh', '-c', command]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if rcs is None:
|
|
Packit Service |
a04d08 |
rcs = (0,)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if description:
|
|
Packit Service |
a04d08 |
LOG.debug('executing "%s"', description)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
LOG.debug("executing command: %s", shell_quote(command))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
out, err, rc = self._execute(command=command, stdin=stdin, env=env)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# False means accept anything.
|
|
Packit Service |
a04d08 |
if (rcs is False or rc in rcs):
|
|
Packit Service |
a04d08 |
return out, err, rc
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
raise InTargetExecuteError(out, err, rc, command, description)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def _execute(self, command, stdin=None, env=None):
|
|
Packit Service |
a04d08 |
"""Execute command in inside, return stdout, stderr and exit code.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
Assumes functional networking and execution as root with the
|
|
Packit Service |
a04d08 |
target filesystem being available at /.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param stdin: bytes content for standard in
|
|
Packit Service |
a04d08 |
@param env: environment variables
|
|
Packit Service |
a04d08 |
@return_value: tuple containing stdout data, stderr data, exit code
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
This is intended to be implemented by the Image or Instance.
|
|
Packit Service |
a04d08 |
Many callers will use the higher level 'execute'."""
|
|
Packit Service |
a04d08 |
raise NotImplementedError("_execute must be implemented by subclass.")
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def read_data(self, remote_path, decode=False):
|
|
Packit Service |
a04d08 |
"""Read data from instance filesystem.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param remote_path: path in instance
|
|
Packit Service |
a04d08 |
@param decode: decode data before returning.
|
|
Packit Service |
a04d08 |
@return_value: content of remote_path as bytes if 'decode' is False,
|
|
Packit Service |
a04d08 |
and as string if 'decode' is True.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
# when sh is invoked with '-c', then the first argument is "$0"
|
|
Packit Service |
a04d08 |
# which is commonly understood as the "program name".
|
|
Packit Service |
a04d08 |
# 'read_data' is the program name, and 'remote_path' is '$1'
|
|
Packit Service |
a04d08 |
stdout, _stderr, rc = self._execute(
|
|
Packit Service |
a04d08 |
["sh", "-c", 'exec cat "$1"', 'read_data', remote_path])
|
|
Packit Service |
a04d08 |
if rc != 0:
|
|
Packit Service |
a04d08 |
raise RuntimeError("Failed to read file '%s'" % remote_path)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if decode:
|
|
Packit Service |
a04d08 |
return stdout.decode()
|
|
Packit Service |
a04d08 |
return stdout
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def write_data(self, remote_path, data):
|
|
Packit Service |
a04d08 |
"""Write data to instance filesystem.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param remote_path: path in instance
|
|
Packit Service |
a04d08 |
@param data: data to write in bytes
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
# when sh is invoked with '-c', then the first argument is "$0"
|
|
Packit Service |
a04d08 |
# which is commonly understood as the "program name".
|
|
Packit Service |
a04d08 |
# 'write_data' is the program name, and 'remote_path' is '$1'
|
|
Packit Service |
a04d08 |
_, _, rc = self._execute(
|
|
Packit Service |
a04d08 |
["sh", "-c", 'exec cat >"$1"', 'write_data', remote_path],
|
|
Packit Service |
a04d08 |
stdin=data)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if rc != 0:
|
|
Packit Service |
a04d08 |
raise RuntimeError("Failed to write to '%s'" % remote_path)
|
|
Packit Service |
a04d08 |
return
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def pull_file(self, remote_path, local_path):
|
|
Packit Service |
a04d08 |
"""Copy file at 'remote_path', from instance to 'local_path'.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param remote_path: path on remote instance
|
|
Packit Service |
a04d08 |
@param local_path: path on local instance
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
with open(local_path, 'wb') as fp:
|
|
Packit Service |
a04d08 |
fp.write(self.read_data(remote_path))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def push_file(self, local_path, remote_path):
|
|
Packit Service |
a04d08 |
"""Copy file at 'local_path' to instance at 'remote_path'.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param local_path: path on local instance
|
|
Packit Service |
a04d08 |
@param remote_path: path on remote instance"""
|
|
Packit Service |
a04d08 |
with open(local_path, "rb") as fp:
|
|
Packit Service |
a04d08 |
self.write_data(remote_path, data=fp.read())
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def run_script(self, script, rcs=None, description=None):
|
|
Packit Service |
a04d08 |
"""Run script in target and return stdout.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param script: script contents
|
|
Packit Service |
a04d08 |
@param rcs: allowed return codes from script
|
|
Packit Service |
a04d08 |
@param description: purpose of script
|
|
Packit Service |
a04d08 |
@return_value: stdout from script
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
# Just write to a file, add execute, run it, then remove it.
|
|
Packit Service |
a04d08 |
shblob = '; '.join((
|
|
Packit Service |
a04d08 |
'set -e',
|
|
Packit Service |
a04d08 |
's="$1"',
|
|
Packit Service |
a04d08 |
'shift',
|
|
Packit Service |
a04d08 |
'cat > "$s"',
|
|
Packit Service |
a04d08 |
'trap "rm -f $s" EXIT',
|
|
Packit Service |
a04d08 |
'chmod +x "$s"',
|
|
Packit Service |
a04d08 |
'"$s" "$@"'))
|
|
Packit Service |
a04d08 |
return self.execute(
|
|
Packit Service |
a04d08 |
['sh', '-c', shblob, 'runscript', self.tmpfile()],
|
|
Packit Service |
a04d08 |
stdin=script, description=description, rcs=rcs)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def tmpfile(self):
|
|
Packit Service |
a04d08 |
"""Get a tmp file in the target.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@return_value: path to new file in target
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
path = "/tmp/%s-%04d" % (type(self).__name__, self._tmp_count)
|
|
Packit Service |
a04d08 |
self._tmp_count += 1
|
|
Packit Service |
a04d08 |
return path
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
11b429 |
class InTargetExecuteError(c_util.ProcessExecutionError):
|
|
Packit Service |
a04d08 |
"""Error type for in target commands that fail."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
default_desc = 'Unexpected error while running command.'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, stdout, stderr, exit_code, cmd, description=None,
|
|
Packit Service |
a04d08 |
reason=None):
|
|
Packit Service |
a04d08 |
"""Init error and parent error class."""
|
|
Packit Service |
a04d08 |
super(InTargetExecuteError, self).__init__(
|
|
Packit Service |
a04d08 |
stdout=stdout, stderr=stderr, exit_code=exit_code,
|
|
Packit Service |
a04d08 |
cmd=shell_quote(cmd),
|
|
Packit Service |
a04d08 |
description=description if description else self.default_desc,
|
|
Packit Service |
a04d08 |
reason=reason)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class PlatformError(IOError):
|
|
Packit Service |
a04d08 |
"""Error type for platform errors."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
default_desc = 'unexpected error in platform.'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, operation, description=None):
|
|
Packit Service |
a04d08 |
"""Init error and parent error class."""
|
|
Packit Service |
a04d08 |
description = description if description else self.default_desc
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
message = '%s: %s' % (operation, description)
|
|
Packit Service |
a04d08 |
IOError.__init__(self, message)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def mkdtemp(prefix='cloud_test_data'):
|
|
Packit Service |
a04d08 |
return tempfile.mkdtemp(prefix=prefix)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
class TempDir(object):
|
|
Packit Service |
a04d08 |
"""Configurable temporary directory like tempfile.TemporaryDirectory."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __init__(self, tmpdir=None, preserve=False, prefix='cloud_test_data_'):
|
|
Packit Service |
a04d08 |
"""Initialize.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param tmpdir: directory to use as tempdir
|
|
Packit Service |
a04d08 |
@param preserve: if true, always preserve data on exit
|
|
Packit Service |
a04d08 |
@param prefix: prefix to use for tempfile name
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
self.tmpdir = tmpdir
|
|
Packit Service |
a04d08 |
self.preserve = preserve
|
|
Packit Service |
a04d08 |
self.prefix = prefix
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __enter__(self):
|
|
Packit Service |
a04d08 |
"""Create tempdir.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@return_value: tempdir path
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
if not self.tmpdir:
|
|
Packit Service |
a04d08 |
self.tmpdir = mkdtemp(prefix=self.prefix)
|
|
Packit Service |
a04d08 |
LOG.debug('using tmpdir: %s', self.tmpdir)
|
|
Packit Service |
a04d08 |
return self.tmpdir
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def __exit__(self, etype, value, trace):
|
|
Packit Service |
a04d08 |
"""Destroy tempdir if no errors occurred."""
|
|
Packit Service |
a04d08 |
if etype or self.preserve:
|
|
Packit Service |
a04d08 |
LOG.info('leaving data in %s', self.tmpdir)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
shutil.rmtree(self.tmpdir)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# vi: ts=4 expandtab
|