tobiko/tobiko/tripleo/_undercloud.py

200 lines
6.6 KiB
Python

# Copyright 2019 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 json
import os
import typing
from oslo_log import log
import tobiko
from tobiko import config
from tobiko.openstack import keystone
from tobiko.shell import ssh
from tobiko.shell import sh
CONF = config.CONF
LOG = log.getLogger(__name__)
def undercloud_ssh_client() -> ssh.SSHClientFixture:
host_config = undercloud_host_config()
if not host_config.hostname:
raise NoSuchUndercloudHostname('No such undercloud hostname')
return ssh.ssh_client(host=host_config.hostname,
**host_config.connect_parameters)
class NoSuchUndercloudHostname(tobiko.TobikoException):
message = "Undercloud hostname not specified"
class InvalidRCFile(tobiko.TobikoException):
message = "Invalid RC file: {rcfile}"
def fetch_os_env(rcfile, *rcfiles) -> typing.Dict[str, str]:
rcfiles = (rcfile,) + rcfiles
LOG.debug('Fetching OS environment variables from TripleO undercloud '
f'host files: {",".join(rcfiles)}')
errors = []
for rcfile in rcfiles:
LOG.debug(f'Reading rcfile: {rcfile}...')
try:
result = sh.execute(f". {rcfile}; env | grep '^OS_'",
ssh_client=undercloud_ssh_client())
except sh.ShellCommandFailed as ex:
LOG.debug(f"Unable to get overcloud RC file '{rcfile}' content "
f"({ex})")
errors.append(tobiko.exc_info)
else:
LOG.debug(f'Parsing environment variables from: {rcfile}...')
env = {}
for line in result.stdout.splitlines():
name, value = line.split('=')
env[name] = value
if env:
env_dump = json.dumps(env, sort_keys=True, indent=4)
LOG.debug(f'Environment variables read from: {rcfile}:\n'
f'{env_dump}')
return env
for error in errors:
LOG.exception(f"Unable to get overcloud RC file '{rcfile}' "
"content", exc_info=error)
raise InvalidRCFile(rcfile=", ".join(rcfiles))
def load_undercloud_rcfile() -> typing.Dict[str, str]:
return fetch_os_env(*CONF.tobiko.tripleo.undercloud_rcfile)
class EnvironUndercloudKeystoneCredentialsFixture(
keystone.EnvironKeystoneCredentialsFixture):
def get_environ(self) -> typing.Dict[str, str]:
return load_undercloud_rcfile()
class CloudsFileUndercloudKeystoneCredentialsFixture(
keystone.CloudsFileKeystoneCredentialsFixture):
def __init__(self, credentials=None, cloud_name=None,
clouds_content=None, clouds_file=None, clouds_files=None):
cloud_name = cloud_name or load_undercloud_rcfile()['OS_CLOUD']
super(CloudsFileUndercloudKeystoneCredentialsFixture, self).__init__(
credentials=credentials, cloud_name=cloud_name,
clouds_content=clouds_content, clouds_file=clouds_file,
clouds_files=clouds_files)
class HasUndercloudFixture(tobiko.SharedFixture):
has_undercloud: typing.Optional[bool] = None
def setup_fixture(self):
self.has_undercloud = check_undercloud()
def check_undercloud() -> bool:
try:
ssh_client = undercloud_ssh_client()
except NoSuchUndercloudHostname:
LOG.debug('TripleO undercloud hostname not found')
return False
try:
ssh_client.connect(retry_count=1,
connection_attempts=1,
timeout=15.)
except Exception as ex:
LOG.debug(f'Unable to connect to TripleO undercloud host: {ex}',
exc_info=1)
return False
LOG.debug('TripleO undercloud host found')
return True
def has_undercloud() -> bool:
return tobiko.setup_fixture(HasUndercloudFixture).has_undercloud
skip_if_missing_undercloud = tobiko.skip_unless(
'TripleO undercloud hostname not configured', has_undercloud)
class UndecloudHostConfig(tobiko.SharedFixture):
hostname: typing.Optional[str] = None
port: typing.Optional[int] = None
username: typing.Optional[str] = None
def __init__(self, **kwargs):
super(UndecloudHostConfig, self).__init__()
self._connect_parameters = ssh.gather_ssh_connect_parameters(**kwargs)
self.key_filename: typing.List[str] = []
def setup_fixture(self):
self.hostname = CONF.tobiko.tripleo.undercloud_ssh_hostname.strip()
self.port = CONF.tobiko.tripleo.undercloud_ssh_port
self.username = CONF.tobiko.tripleo.undercloud_ssh_username
self.key_filename = self.get_key_filenames()
@staticmethod
def get_key_filenames() -> typing.List[str]:
key_filenames: typing.List[str] = []
conf = tobiko.tobiko_config()
key_filename = conf.tripleo.undercloud_ssh_key_filename
if key_filename:
key_filename = tobiko.tobiko_config_path(key_filename)
if os.path.isfile(key_filename):
key_filenames.append(key_filename)
key_filenames.extend(ssh.list_proxy_jump_key_filenames())
key_filenames.extend(ssh.list_key_filenames())
return tobiko.select_uniques(key_filenames)
@property
def connect_parameters(self):
parameters = ssh.gather_ssh_connect_parameters(self)
parameters.update(self._connect_parameters)
return parameters
def undercloud_host_config() -> UndecloudHostConfig:
return tobiko.setup_fixture(UndecloudHostConfig)
def undercloud_keystone_client():
session = undercloud_keystone_session()
return keystone.get_keystone_client(session=session)
def _get_keystone_credentials():
environ = load_undercloud_rcfile()
if 'OS_CLOUD' in environ:
credentials = CloudsFileUndercloudKeystoneCredentialsFixture
else:
credentials = EnvironUndercloudKeystoneCredentialsFixture
return credentials
def undercloud_keystone_session():
return keystone.get_keystone_session(
credentials=_get_keystone_credentials())
def undercloud_keystone_credentials():
return tobiko.setup_fixture(_get_keystone_credentials()).credentials