190 lines
6.4 KiB
Python
190 lines
6.4 KiB
Python
# Copyright 2020 Red Hat
|
|
#
|
|
# 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 io
|
|
import os
|
|
import re
|
|
import typing
|
|
|
|
from oslo_log import log
|
|
from packaging import version
|
|
|
|
import tobiko
|
|
from tobiko.shell import ansible
|
|
from tobiko.shell import sh
|
|
from tobiko.shell import ssh
|
|
from tobiko.tripleo import _overcloud
|
|
from tobiko.tripleo import _undercloud
|
|
from tobiko.tripleo import _config
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
def get_tripleo_ansible_inventory():
|
|
inventory_file = get_tripleo_ansible_inventory_file()
|
|
with io.open(inventory_file, 'rb') as fd:
|
|
return tobiko.load_yaml(fd)
|
|
|
|
|
|
def has_tripleo_ansible_inventory() -> bool:
|
|
inventory_file = get_tripleo_ansible_inventory_file()
|
|
return inventory_file is not None
|
|
|
|
|
|
skip_if_missing_tripleo_ansible_inventory = \
|
|
tobiko.skip_unless("Can't read TripleO Ansible inventory",
|
|
has_tripleo_ansible_inventory)
|
|
|
|
|
|
@functools.lru_cache()
|
|
def get_tripleo_ansible_inventory_file() -> typing.Optional[str]:
|
|
if _undercloud.has_undercloud():
|
|
inventory_file = _config.get_tripleo_config().inventory_file
|
|
if inventory_file:
|
|
inventory_file = tobiko.tobiko_config_path(inventory_file)
|
|
fetch_tripleo_inventary_file(inventory_file=inventory_file)
|
|
return inventory_file
|
|
return None
|
|
|
|
|
|
def skip_unless_undercloud_has_ansible(min_version: float = None):
|
|
reason = ("Ansible not found on undercloud host "
|
|
f"(min_version={min_version})")
|
|
return tobiko.skip_unless(reason=reason,
|
|
predicate=has_undercloud_ansible_version,
|
|
min_version=min_version)
|
|
|
|
|
|
def has_undercloud_ansible_version(min_version: float = None) -> bool:
|
|
ssh_client = _undercloud.undercloud_ssh_client()
|
|
try:
|
|
output = sh.execute('ansible --version',
|
|
ssh_client=ssh_client).stdout
|
|
except sh.ShellCommandFailed:
|
|
LOG.debug("Error getting ansible version", exc_info=1)
|
|
return False
|
|
|
|
if min_version is not None:
|
|
first_line = output.splitlines()[0]
|
|
# obtain the ansible version with a regex
|
|
# the expected output can have a format like these
|
|
# ansible 2.9.27
|
|
# ansible [core 2.12.2]
|
|
search_version = re.search(r'(\d+\.)?(\d+\.)?(\*|\d+)', first_line)
|
|
if not search_version:
|
|
return False
|
|
ansible_version_full = search_version.group(0)
|
|
version_pair = ansible_version_full.split('.', 2)[:2]
|
|
# compare versions rather than floats - example of a posible mistake:
|
|
# version 2.12 is higher than version 2.9
|
|
current_version = version.parse('.'.join(version_pair))
|
|
if current_version < version.parse(str(min_version)):
|
|
LOG.debug(f"Ansible version is < {min_version}:\n"
|
|
f"{first_line}\n")
|
|
return False
|
|
return True
|
|
|
|
|
|
READ_TRIPLEO_ANSIBLE_INVENTORY_SCRIPT = """
|
|
source {undercloud_rcfile} || exit 1
|
|
|
|
set -x
|
|
|
|
if [ "$OS_CLOUD" == "" ]; then
|
|
# on some releases tripleo-ansible-inventory command is supported
|
|
INVENTORY_FILE=$(mktemp tripleo-hosts-XXXXXXXXXX.yaml)
|
|
tripleo-ansible-inventory --ansible_ssh_user "{overcloud_ssh_username}" \\
|
|
--static-yaml-inventory "$INVENTORY_FILE"
|
|
RC=$?
|
|
|
|
if [ $RC == 0 ]; then
|
|
cat "$INVENTORY_FILE"
|
|
fi
|
|
rm -fR "$INVENTORY_FILE"
|
|
exit $RC
|
|
fi
|
|
|
|
# on latest releases, the updated inventory can always be found into the
|
|
# following path
|
|
TAI_DIR=~/overcloud-deploy/overcloud/config-download/overcloud
|
|
cat $TAI_DIR/tripleo-ansible-inventory.yaml
|
|
"""
|
|
|
|
|
|
def read_tripleo_ansible_inventory():
|
|
tripleo = _config.get_tripleo_config()
|
|
ssh_client = _undercloud.undercloud_ssh_client()
|
|
script = READ_TRIPLEO_ANSIBLE_INVENTORY_SCRIPT.format(
|
|
undercloud_rcfile=tripleo.undercloud_rcfile[0],
|
|
overcloud_ssh_username=_overcloud.get_overcloud_ssh_username())
|
|
return sh.execute('/bin/bash', stdin=script, ssh_client=ssh_client).stdout
|
|
|
|
|
|
def fetch_tripleo_inventary_file(inventory_file: str):
|
|
content = read_tripleo_ansible_inventory()
|
|
tobiko.makedirs(os.path.dirname(inventory_file))
|
|
with io.open(inventory_file, 'w') as fd:
|
|
fd.write(content)
|
|
|
|
|
|
class UndercloudAnsiblePlaybook(ansible.AnsiblePlaybook):
|
|
|
|
@property
|
|
def ssh_client(self) -> ssh.SSHClientType:
|
|
return _undercloud.undercloud_ssh_client()
|
|
|
|
def _ensure_inventory_files(self, *inventory_filenames: str) \
|
|
-> typing.List[str]:
|
|
inventory_file = get_tripleo_ansible_inventory_file()
|
|
if inventory_file is not None:
|
|
inventory_filenames += (inventory_file,)
|
|
return super()._ensure_inventory_files(*inventory_filenames)
|
|
|
|
|
|
def undercloud_ansible_playbook() -> UndercloudAnsiblePlaybook:
|
|
return tobiko.get_fixture(UndercloudAnsiblePlaybook)
|
|
|
|
|
|
def run_playbook_from_undercloud(
|
|
command: sh.ShellCommand = None,
|
|
playbook: str = None,
|
|
playbook_dirname: str = None,
|
|
playbook_filename: str = None,
|
|
inventory_filenames: typing.Iterable[str] = None,
|
|
playbook_files: typing.Iterable[str] = None,
|
|
requirements_files: typing.Iterable[str] = None,
|
|
roles: typing.Iterable[str] = None,
|
|
roles_path: typing.Iterable[str] = None,
|
|
verbosity: int = None):
|
|
return undercloud_ansible_playbook().run_playbook(
|
|
command=command,
|
|
playbook=playbook,
|
|
playbook_dirname=playbook_dirname,
|
|
playbook_filename=playbook_filename,
|
|
inventory_filenames=inventory_filenames,
|
|
playbook_files=playbook_files,
|
|
requirements_files=requirements_files,
|
|
roles=roles,
|
|
roles_path=roles_path,
|
|
verbosity=verbosity)
|
|
|
|
|
|
def setup_undercloud_ansible_playbook():
|
|
if _undercloud.has_undercloud():
|
|
ansible.register_ansible_playbook(
|
|
undercloud_ansible_playbook())
|