Merge "Run Ansible playbook from undercloud node"

This commit is contained in:
Zuul 2022-03-15 12:08:14 +00:00 committed by Gerrit Code Review
commit d1fac1802c
10 changed files with 387 additions and 21 deletions

View File

@ -0,0 +1,24 @@
# Copyright (c) 2022 Red Hat, 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.
from __future__ import absolute_import
from tobiko.shell.ansible import _playbook
ansible_playbook = _playbook.ansible_playbook
AnsiblePlaybook = _playbook.AnsiblePlaybook
register_ansible_playbook = _playbook.register_ansible_playbook
run_playbook = _playbook.run_playbook

View File

@ -0,0 +1,236 @@
# Copyright (c) 2022 Red Hat, 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.
from __future__ import absolute_import
import os
import typing
import tobiko
from tobiko.shell import sh
from tobiko.shell import ssh
class AnsiblePlaybook(tobiko.SharedFixture):
def __init__(self,
command: sh.ShellCommandType = 'ansible-playbook',
inventory_filename: str = None,
playbook: str = 'main',
playbook_dirname: str = None,
ssh_client: ssh.SSHClientType = None,
work_dir: str = None):
super(AnsiblePlaybook, self).__init__()
self._command = sh.shell_command(command)
self._inventory_filename = inventory_filename
self._playbook = playbook
self._playbook_dirname = playbook_dirname
self._ssh_client = ssh_client
self._work_dir = work_dir
self._work_files: typing.Dict[str, str] = {}
def setup_fixture(self):
pass
@property
def ssh_client(self) -> ssh.SSHClientType:
return self._ssh_client
_sh_connection: typing.Optional[sh.ShellConnection] = None
@property
def sh_connection(self) -> sh.ShellConnection:
if self._sh_connection is None:
self._sh_connection = sh.shell_connection(
ssh_client=self.ssh_client)
return self._sh_connection
@property
def work_dir(self) -> str:
if self._work_dir is None:
self._work_dir = self.sh_connection.make_temp_dir(auto_clean=False)
self._work_files = {}
return self._work_dir
@property
def playbook_dirname(self) -> str:
return tobiko.check_valid_type(self._playbook_dirname, str)
def _ensure_inventory_filename(self, inventory_filename: str = None) \
-> typing.Optional[str]:
if inventory_filename is None:
inventory_filename = self._inventory_filename
if inventory_filename is None:
return None
return self._ensure_work_file(inventory_filename, 'inventory')
def _ensure_playbook_filename(self,
playbook: str = None,
playbook_dirname: str = None,
playbook_filename: str = None) \
-> typing.Optional[str]:
if playbook_filename is None:
playbook_filename = self._get_playbook_filename(
playbook=playbook, playbook_dirname=playbook_dirname)
return self._ensure_work_file(playbook_filename)
def _get_playbook_filename(self,
playbook: str = None,
playbook_dirname: str = None) -> str:
if playbook is None:
playbook = self._playbook
if playbook_dirname is None:
playbook_dirname = self.playbook_dirname
return os.path.join(playbook_dirname, playbook + '.yaml')
def _ensure_work_file(self, filename: str, sub_dir: str = None) -> str:
filename = os.path.realpath(filename)
work_filename = self._work_files.get(filename)
if work_filename is None:
if sub_dir is not None:
work_filename = os.path.join(
self.work_dir, sub_dir, os.path.basename(filename))
else:
work_filename = os.path.join(
self.work_dir, os.path.basename(filename))
if sub_dir is not None:
self.sh_connection.make_dirs(os.path.dirname(work_filename))
self.sh_connection.put_file(filename, work_filename)
self._work_files[filename] = work_filename
return work_filename
def cleanup_fixture(self):
self._sh_connection = None
self._work_files = None
if self._work_dir is not None:
self.sh_connection.remove_files(self._work_dir)
self._work_dir = None
def _get_command(self,
command: sh.ShellCommandType = None,
playbook: str = None,
playbook_dirname: str = None,
playbook_filename: str = None,
inventory_filename: str = None) -> \
sh.ShellCommand:
# ensure command
if command is None:
command = self._command
assert isinstance(command, sh.ShellCommand)
# ensure inventory
work_inventory_filename = self._ensure_inventory_filename(
inventory_filename)
if work_inventory_filename is not None:
command += ['-i', work_inventory_filename]
# ensure playbook file
command += [self._ensure_playbook_filename(
playbook=playbook,
playbook_dirname=playbook_dirname,
playbook_filename=playbook_filename)]
return command
def run_playbook(self,
command: sh.ShellCommand = None,
playbook: str = None,
playbook_dirname: str = None,
playbook_filename: str = None,
inventory_filename: str = None):
tobiko.setup_fixture(self)
command = self._get_command(command=command,
playbook=playbook,
playbook_dirname=playbook_dirname,
playbook_filename=playbook_filename,
inventory_filename=inventory_filename)
return self.sh_connection.execute(command, current_dir=self.work_dir)
def local_ansible_playbook() -> 'AnsiblePlaybook':
return tobiko.get_fixture(AnsiblePlaybook)
def ansible_playbook(ssh_client: ssh.SSHClientType = None,
manager: 'AnsiblePlaybookManager' = None) -> \
'AnsiblePlaybook':
return ansible_playbook_manager(manager).get_ansible_playbook(
ssh_client=ssh_client)
def register_ansible_playbook(playbook: 'AnsiblePlaybook',
manager: 'AnsiblePlaybookManager' = None) -> \
None:
tobiko.check_valid_type(playbook, AnsiblePlaybook)
ansible_playbook_manager(manager).register_ansible_playbook(playbook)
def ansible_playbook_manager(manager: 'AnsiblePlaybookManager' = None) -> \
'AnsiblePlaybookManager':
if manager is None:
return tobiko.setup_fixture(AnsiblePlaybookManager)
else:
tobiko.check_valid_type(manager, AnsiblePlaybookManager)
return manager
AnsiblePlaybookKey = typing.Optional[ssh.SSHClientFixture]
class AnsiblePlaybookManager(tobiko.SharedFixture):
def __init__(self):
super(AnsiblePlaybookManager, self).__init__()
self._host_playbooks: typing.Dict['AnsiblePlaybookKey',
'AnsiblePlaybook'] = {}
def get_ansible_playbook(self,
ssh_client: ssh.SSHClientType) -> \
'AnsiblePlaybook':
ssh_client = ssh.ssh_client_fixture(ssh_client)
playbook = self._host_playbooks.get(ssh_client)
if playbook is None:
playbook = self._init_ansible_playbook(ssh_client=ssh_client)
self._host_playbooks[ssh_client] = playbook
return playbook
def register_ansible_playbook(self, playbook: 'AnsiblePlaybook'):
ssh_client = ssh.ssh_client_fixture(playbook.ssh_client)
self._host_playbooks[ssh_client] = playbook
@staticmethod
def _init_ansible_playbook(ssh_client: ssh.SSHClientFixture = None) \
-> 'AnsiblePlaybook':
if ssh_client is None:
return local_ansible_playbook()
else:
return tobiko.get_fixture(AnsiblePlaybook(ssh_client=ssh_client))
def run_playbook(command: sh.ShellCommand = None,
playbook: str = None,
playbook_dirname: str = None,
playbook_filename: str = None,
inventory_filename: str = None,
ssh_client: ssh.SSHClientType = None,
manager: AnsiblePlaybookManager = None) \
-> sh.ShellExecuteResult:
return ansible_playbook(ssh_client=ssh_client,
manager=manager).run_playbook(
command=command,
playbook=playbook,
playbook_dirname=playbook_dirname,
playbook_filename=playbook_filename,
inventory_filename=inventory_filename)

View File

@ -23,7 +23,6 @@ from tobiko.shell.sh import _execute
from tobiko.shell.sh import _hostname
from tobiko.shell.sh import _io
from tobiko.shell.sh import _local
from tobiko.shell.sh import _mktemp
from tobiko.shell.sh import _nameservers
from tobiko.shell.sh import _path
from tobiko.shell.sh import _process
@ -54,6 +53,8 @@ is_cirros_connection = _connection.is_cirros_connection
is_local_connection = _connection.is_local_connection
put_file = _connection.put_file
get_file = _connection.get_file
make_temp_dir = _connection.make_temp_dir
make_dirs = _connection.make_dirs
ShellError = _exception.ShellError
ShellCommandFailed = _exception.ShellCommandFailed
@ -80,8 +81,6 @@ local_process = _local.local_process
LocalShellProcessFixture = _local.LocalShellProcessFixture
LocalExecutePathFixture = _local.LocalExecutePathFixture
make_temp_dir = _mktemp.make_temp_dir
ListNameserversFixture = _nameservers.ListNameserversFixture
list_nameservers = _nameservers.list_nameservers

View File

@ -29,6 +29,7 @@ import tobiko
from tobiko.shell.sh import _command
from tobiko.shell.sh import _execute
from tobiko.shell.sh import _hostname
from tobiko.shell.sh import _mktemp
from tobiko.shell import ssh
@ -72,9 +73,10 @@ def put_file(local_file: str,
def make_temp_dir(ssh_client: ssh.SSHClientType = None,
auto_clean=True) -> str:
auto_clean=True,
sudo: bool = None) -> str:
return shell_connection(ssh_client=ssh_client).make_temp_dir(
auto_clean=auto_clean)
auto_clean=auto_clean, sudo=sudo)
def remove_files(filename: str, *filenames: str,
@ -83,6 +85,13 @@ def remove_files(filename: str, *filenames: str,
filename, *filenames)
def make_dirs(name: str,
exist_ok=True,
ssh_client: ssh.SSHClientType = None) -> str:
return shell_connection(ssh_client=ssh_client).make_dirs(
name=name, exist_ok=exist_ok)
def local_shell_connection() -> 'LocalShellConnection':
return tobiko.get_fixture(LocalShellConnection)
@ -194,12 +203,15 @@ class ShellConnection(tobiko.SharedFixture):
def __str__(self) -> str:
return f"{type(self).__name__}<{self.login}>"
def make_temp_dir(self, auto_clean=True) -> str:
def make_temp_dir(self, auto_clean=True, sudo: bool = None) -> str:
raise NotImplementedError
def remove_files(self, filename: str, *filenames: str):
raise NotImplementedError
def make_dirs(self, name: str, exist_ok=True):
raise NotImplementedError
class LocalShellConnection(ShellConnection):
@ -229,13 +241,17 @@ class LocalShellConnection(ShellConnection):
f"'{local_file}' ...")
shutil.copyfile(remote_file, local_file)
def make_temp_dir(self, auto_clean=True) -> str:
temp_dir = tempfile.mkdtemp()
LOG.debug(f"Local temporary directory created as {self.login}: "
f"{temp_dir}")
if auto_clean:
tobiko.add_cleanup(self.remove_files, temp_dir)
return temp_dir
def make_temp_dir(self, auto_clean=True, sudo: bool = None) -> str:
if sudo:
return _mktemp.make_temp_dir(ssh_client=self.ssh_client,
sudo=True)
else:
temp_dir = tempfile.mkdtemp()
LOG.debug(f"Local temporary directory created as {self.login}: "
f"{temp_dir}")
if auto_clean:
tobiko.add_cleanup(self.remove_files, temp_dir)
return temp_dir
def remove_files(self, filename: str, *filenames: str):
filenames = (filename,) + filenames
@ -244,6 +260,10 @@ class LocalShellConnection(ShellConnection):
if os.path.exists(filename):
shutil.rmtree(filename)
def make_dirs(self, name: str, exist_ok=True):
os.makedirs(name=name,
exist_ok=exist_ok)
class SSHShellConnection(ShellConnection):
@ -298,8 +318,8 @@ class SSHShellConnection(ShellConnection):
f"'{local_file}'...")
self.sftp_client.get(remote_file, local_file)
def make_temp_dir(self, auto_clean=True) -> str:
temp_dir = self.execute('mktemp -d').stdout.strip()
def make_temp_dir(self, auto_clean=True, sudo: bool = None) -> str:
temp_dir = self.execute('mktemp -d', sudo=sudo).stdout.strip()
LOG.debug(f"Remote temporary directory created as {self.login}: "
f"{temp_dir}")
if auto_clean:
@ -311,3 +331,10 @@ class SSHShellConnection(ShellConnection):
LOG.debug(f"Remove remote files as {self.login}: {filenames}")
command = _command.shell_command('rm -fR') + filenames
self.execute(command)
def make_dirs(self, name: str, exist_ok=True):
command = _command.shell_command('mkdir')
if exist_ok:
command += '-p'
command += name
self.execute(command)

View File

@ -0,0 +1,6 @@
---
- hosts: all
tasks:
- name: 'Ping shiftstack hosts'
ping:

View File

@ -0,0 +1,33 @@
# Copyright (c) 2022 Red Hat, 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 os
import testtools
from tobiko import tripleo
PLAYBOOK_DIRNAME = os.path.join(os.path.dirname(__file__), 'playbooks')
@tripleo.skip_if_missing_undercloud
class OpenShiftTest(testtools.TestCase):
@tripleo.skip_if_missing_tripleo_ansible_inventory
def test_ping_all_hosts(self):
tripleo.run_playbook_from_undercloud(
playbook='ping-shiftstack',
playbook_dirname=PLAYBOOK_DIRNAME)

View File

@ -13,19 +13,20 @@
# under the License.
from __future__ import absolute_import
from tobiko.tripleo import _ansible
from tobiko.tripleo import _ansible as ansible
from tobiko.tripleo import _overcloud as overcloud
from tobiko.tripleo import _topology as topology
from tobiko.tripleo import _undercloud as undercloud
get_tripleo_ansible_inventory = _ansible.get_tripleo_ansible_inventory
get_tripleo_ansible_inventory = ansible.get_tripleo_ansible_inventory
get_tripleo_ansible_inventory_file = \
_ansible.get_tripleo_ansible_inventory_file
has_tripleo_ansible_inventory = _ansible.has_tripleo_ansible_inventory
read_tripleo_ansible_inventory = _ansible.read_tripleo_ansible_inventory
ansible.get_tripleo_ansible_inventory_file
has_tripleo_ansible_inventory = ansible.has_tripleo_ansible_inventory
read_tripleo_ansible_inventory = ansible.read_tripleo_ansible_inventory
skip_if_missing_tripleo_ansible_inventory = \
_ansible.skip_if_missing_tripleo_ansible_inventory
ansible.skip_if_missing_tripleo_ansible_inventory
run_playbook_from_undercloud = ansible.run_playbook_from_undercloud
find_overcloud_node = overcloud.find_overcloud_node
list_overcloud_nodes = overcloud.list_overcloud_nodes

View File

@ -17,7 +17,9 @@ import io
import os
import tobiko
from tobiko.shell import ansible
from tobiko.shell import sh
from tobiko.shell import ssh
from tobiko.tripleo import _undercloud
from tobiko.tripleo import _config
@ -81,3 +83,37 @@ def read_tripleo_ansible_inventory():
undercloud_rcfile=tripleo.undercloud_rcfile[0],
overcloud_ssh_username=tripleo.overcloud_ssh_username)
return sh.execute('/bin/bash', stdin=script, ssh_client=ssh_client).stdout
class UndercloudAnsiblePlaybook(ansible.AnsiblePlaybook):
@property
def ssh_client(self) -> ssh.SSHClientType:
return _undercloud.undercloud_ssh_client()
def setup_fixture(self):
self._inventory_filename = get_tripleo_ansible_inventory_file()
super(UndercloudAnsiblePlaybook, self).setup_fixture()
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_filename: str = None):
return undercloud_ansible_playbook().run_playbook(
command=command,
playbook=playbook,
playbook_dirname=playbook_dirname,
playbook_filename=playbook_filename,
inventory_filename=inventory_filename)
def setup_undercloud_ansible_plabook():
if _undercloud.has_undercloud():
ansible.register_ansible_playbook(
undercloud_ansible_playbook())

View File

@ -17,6 +17,7 @@ import itertools
from oslo_config import cfg
GROUP_NAME = 'tripleo'
OPTIONS = [
# Undercloud options
@ -74,7 +75,10 @@ def list_options():
def setup_tobiko_config(conf):
# pylint: disable=unused-argument
from tobiko.tripleo import ansible
from tobiko.tripleo import overcloud
from tobiko.tripleo import topology
ansible.setup_undercloud_ansible_plabook()
overcloud.setup_overcloud_keystone_crederntials()
topology.setup_tripleo_topology()