ansible/test/lib/ansible_test/_internal/cloud/openshift.py
Matt Clay 3c2e8b99be
Fix ansible-test handling of user-defined docker networks. (#72256)
* Fix ansible-test docker container detection.

* Attach test containers to the correct network.

* Do not assume `localhost` for accesing Docker.

* Look for containers on current network.

* Always map /var/run/docker.sock into containers.

This fixes issues when using a remote Docker host.

* Support container IP lookup from networks list.

* Fix container network attachment.

* Remove redundant container detection messages.

* Limit DOCKER_HOST parsing to TCP.

* Restore docker socket existence check.

The check is skipped if the docker hostname is not localhost.

* Correct changelog entry.
2020-10-22 18:45:03 -07:00

236 lines
7.5 KiB
Python

"""OpenShift plugin for integration tests."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import os
import re
import time
from . import (
CloudProvider,
CloudEnvironment,
CloudEnvironmentConfig,
)
from ..io import (
read_text_file,
)
from ..util import (
find_executable,
ApplicationError,
display,
SubprocessError,
)
from ..http import (
HttpClient,
)
from ..docker_util import (
docker_exec,
docker_run,
docker_rm,
docker_inspect,
docker_pull,
docker_network_inspect,
get_docker_container_id,
get_docker_preferred_network_name,
get_docker_hostname,
is_docker_user_defined_network,
)
class OpenShiftCloudProvider(CloudProvider):
"""OpenShift cloud provider plugin. Sets up cloud resources before delegation."""
DOCKER_CONTAINER_NAME = 'openshift-origin'
def __init__(self, args):
"""
:type args: TestConfig
"""
super(OpenShiftCloudProvider, self).__init__(args, config_extension='.kubeconfig')
# The image must be pinned to a specific version to guarantee CI passes with the version used.
self.image = 'openshift/origin:v3.9.0'
self.container_name = ''
def filter(self, targets, exclude):
"""Filter out the cloud tests when the necessary config and resources are not available.
:type targets: tuple[TestTarget]
:type exclude: list[str]
"""
if os.path.isfile(self.config_static_path):
return
docker = find_executable('docker', required=False)
if docker:
return
skip = 'cloud/%s/' % self.platform
skipped = [target.name for target in targets if skip in target.aliases]
if skipped:
exclude.append(skip)
display.warning('Excluding tests marked "%s" which require the "docker" command or config (see "%s"): %s'
% (skip.rstrip('/'), self.config_template_path, ', '.join(skipped)))
def setup(self):
"""Setup the cloud resource before delegation and register a cleanup callback."""
super(OpenShiftCloudProvider, self).setup()
if self._use_static_config():
self._setup_static()
else:
self._setup_dynamic()
def get_remote_ssh_options(self):
"""Get any additional options needed when delegating tests to a remote instance via SSH.
:rtype: list[str]
"""
if self.managed:
return ['-R', '8443:%s:8443' % get_docker_hostname()]
return []
def get_docker_run_options(self):
"""Get any additional options needed when delegating tests to a docker container.
:rtype: list[str]
"""
network = get_docker_preferred_network_name(self.args)
if self.managed and not is_docker_user_defined_network(network):
return ['--link', self.DOCKER_CONTAINER_NAME]
return []
def cleanup(self):
"""Clean up the cloud resource and any temporary configuration files after tests complete."""
if self.container_name:
docker_rm(self.args, self.container_name)
super(OpenShiftCloudProvider, self).cleanup()
def _setup_static(self):
"""Configure OpenShift tests for use with static configuration."""
config = read_text_file(self.config_static_path)
match = re.search(r'^ *server: (?P<server>.*)$', config, flags=re.MULTILINE)
if match:
endpoint = match.group('server')
self._wait_for_service(endpoint)
else:
display.warning('Could not find OpenShift endpoint in kubeconfig. Skipping check for OpenShift service availability.')
def _setup_dynamic(self):
"""Create a OpenShift container using docker."""
self.container_name = self.DOCKER_CONTAINER_NAME
results = docker_inspect(self.args, self.container_name)
if results and not results[0]['State']['Running']:
docker_rm(self.args, self.container_name)
results = []
if results:
display.info('Using the existing OpenShift docker container.', verbosity=1)
else:
display.info('Starting a new OpenShift docker container.', verbosity=1)
docker_pull(self.args, self.image)
cmd = ['start', 'master', '--listen', 'https://0.0.0.0:8443']
docker_run(self.args, self.image, ['-d', '-p', '8443:8443', '--name', self.container_name], cmd)
container_id = get_docker_container_id()
if container_id:
host = self._get_container_address()
display.info('Found OpenShift container address: %s' % host, verbosity=1)
else:
host = get_docker_hostname()
port = 8443
endpoint = 'https://%s:%s/' % (host, port)
self._wait_for_service(endpoint)
if self.args.explain:
config = '# Unknown'
else:
if self.args.docker:
host = self.DOCKER_CONTAINER_NAME
elif self.args.remote:
host = 'localhost'
server = 'https://%s:%s' % (host, port)
config = self._get_config(server)
self._write_config(config)
def _get_container_address(self):
current_network = get_docker_preferred_network_name(self.args)
networks = docker_network_inspect(self.args, current_network)
try:
network = [network for network in networks if network['Name'] == current_network][0]
containers = network['Containers']
container = [containers[container] for container in containers if containers[container]['Name'] == self.DOCKER_CONTAINER_NAME][0]
return re.sub(r'/[0-9]+$', '', container['IPv4Address'])
except Exception:
display.error('Failed to process the following docker network inspect output:\n%s' %
json.dumps(networks, indent=4, sort_keys=True))
raise
def _wait_for_service(self, endpoint):
"""Wait for the OpenShift service endpoint to accept connections.
:type endpoint: str
"""
if self.args.explain:
return
client = HttpClient(self.args, always=True, insecure=True)
for dummy in range(1, 30):
display.info('Waiting for OpenShift service: %s' % endpoint, verbosity=1)
try:
client.get(endpoint)
return
except SubprocessError:
pass
time.sleep(10)
raise ApplicationError('Timeout waiting for OpenShift service.')
def _get_config(self, server):
"""Get OpenShift config from container.
:type server: str
:rtype: dict[str, str]
"""
cmd = ['cat', '/var/lib/origin/openshift.local.config/master/admin.kubeconfig']
stdout, dummy = docker_exec(self.args, self.container_name, cmd, capture=True)
config = stdout
config = re.sub(r'^( *)certificate-authority-data: .*$', r'\1insecure-skip-tls-verify: true', config, flags=re.MULTILINE)
config = re.sub(r'^( *)server: .*$', r'\1server: %s' % server, config, flags=re.MULTILINE)
return config
class OpenShiftCloudEnvironment(CloudEnvironment):
"""OpenShift cloud environment plugin. Updates integration test environment after delegation."""
def get_environment_config(self):
"""
:rtype: CloudEnvironmentConfig
"""
env_vars = dict(
K8S_AUTH_KUBECONFIG=self.config_path,
)
return CloudEnvironmentConfig(
env_vars=env_vars,
)