Overhaul httptester support in ansible-test. (#39892)
- Works with the --remote option. - Can be disabled with the --disable-httptester option. - Change image with the --httptester option. - Only load and run httptester for targets that require it.
This commit is contained in:
parent
3c32b483bc
commit
c1f9efabf4
10 changed files with 313 additions and 47 deletions
|
@ -1,2 +1,3 @@
|
||||||
destructive
|
destructive
|
||||||
posix/ci/group1
|
posix/ci/group1
|
||||||
|
needs/httptester
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
posix/ci/group2
|
posix/ci/group2
|
||||||
|
needs/httptester
|
||||||
|
|
|
@ -46,4 +46,26 @@
|
||||||
command: update-ca-certificates
|
command: update-ca-certificates
|
||||||
when: ansible_os_family == 'Debian' or ansible_os_family == 'Suse'
|
when: ansible_os_family == 'Debian' or ansible_os_family == 'Suse'
|
||||||
|
|
||||||
|
- name: FreeBSD - Retrieve test cacert
|
||||||
|
get_url:
|
||||||
|
url: "http://ansible.http.tests/cacert.pem"
|
||||||
|
dest: "/tmp/ansible.pem"
|
||||||
|
when: ansible_os_family == 'FreeBSD'
|
||||||
|
|
||||||
|
- name: FreeBSD - Add cacert to root certificate store
|
||||||
|
blockinfile:
|
||||||
|
path: "/etc/ssl/cert.pem"
|
||||||
|
block: "{{ lookup('file', '/tmp/ansible.pem') }}"
|
||||||
|
when: ansible_os_family == 'FreeBSD'
|
||||||
|
|
||||||
|
- name: MacOS - Retrieve test cacert
|
||||||
|
get_url:
|
||||||
|
url: "http://ansible.http.tests/cacert.pem"
|
||||||
|
dest: "/usr/local/etc/openssl/certs/ansible.pem"
|
||||||
|
when: ansible_os_family == 'Darwin'
|
||||||
|
|
||||||
|
- name: MacOS - Update ca certificates
|
||||||
|
command: /usr/local/opt/openssl/bin/c_rehash
|
||||||
|
when: ansible_os_family == 'Darwin'
|
||||||
|
|
||||||
when: has_httptester|bool
|
when: has_httptester|bool
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
destructive
|
destructive
|
||||||
posix/ci/group1
|
posix/ci/group1
|
||||||
|
needs/httptester
|
||||||
|
|
|
@ -43,7 +43,6 @@ class EnvironmentConfig(CommonConfig):
|
||||||
self.remote = args.remote # type: str
|
self.remote = args.remote # type: str
|
||||||
|
|
||||||
self.docker_privileged = args.docker_privileged if 'docker_privileged' in args else False # type: bool
|
self.docker_privileged = args.docker_privileged if 'docker_privileged' in args else False # type: bool
|
||||||
self.docker_util = docker_qualify_image(args.docker_util if 'docker_util' in args else '') # type: str
|
|
||||||
self.docker_pull = args.docker_pull if 'docker_pull' in args else False # type: bool
|
self.docker_pull = args.docker_pull if 'docker_pull' in args else False # type: bool
|
||||||
self.docker_keep_git = args.docker_keep_git if 'docker_keep_git' in args else False # type: bool
|
self.docker_keep_git = args.docker_keep_git if 'docker_keep_git' in args else False # type: bool
|
||||||
self.docker_memory = args.docker_memory if 'docker_memory' in args else None
|
self.docker_memory = args.docker_memory if 'docker_memory' in args else None
|
||||||
|
@ -70,6 +69,9 @@ class EnvironmentConfig(CommonConfig):
|
||||||
if self.delegate:
|
if self.delegate:
|
||||||
self.requirements = True
|
self.requirements = True
|
||||||
|
|
||||||
|
self.inject_httptester = args.inject_httptester if 'inject_httptester' in args else False # type: bool
|
||||||
|
self.httptester = docker_qualify_image(args.httptester if 'httptester' in args else '') # type: str
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def python_executable(self):
|
def python_executable(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,7 +12,10 @@ import lib.thread
|
||||||
|
|
||||||
from lib.executor import (
|
from lib.executor import (
|
||||||
SUPPORTED_PYTHON_VERSIONS,
|
SUPPORTED_PYTHON_VERSIONS,
|
||||||
|
HTTPTESTER_HOSTS,
|
||||||
create_shell_command,
|
create_shell_command,
|
||||||
|
run_httptester,
|
||||||
|
start_httptester,
|
||||||
)
|
)
|
||||||
|
|
||||||
from lib.config import (
|
from lib.config import (
|
||||||
|
@ -37,6 +40,7 @@ from lib.util import (
|
||||||
run_command,
|
run_command,
|
||||||
common_environment,
|
common_environment,
|
||||||
pass_vars,
|
pass_vars,
|
||||||
|
display,
|
||||||
)
|
)
|
||||||
|
|
||||||
from lib.docker_util import (
|
from lib.docker_util import (
|
||||||
|
@ -46,18 +50,24 @@ from lib.docker_util import (
|
||||||
docker_put,
|
docker_put,
|
||||||
docker_rm,
|
docker_rm,
|
||||||
docker_run,
|
docker_run,
|
||||||
|
docker_available,
|
||||||
)
|
)
|
||||||
|
|
||||||
from lib.cloud import (
|
from lib.cloud import (
|
||||||
get_cloud_providers,
|
get_cloud_providers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from lib.target import (
|
||||||
|
IntegrationTarget,
|
||||||
|
)
|
||||||
|
|
||||||
def delegate(args, exclude, require):
|
|
||||||
|
def delegate(args, exclude, require, integration_targets):
|
||||||
"""
|
"""
|
||||||
:type args: EnvironmentConfig
|
:type args: EnvironmentConfig
|
||||||
:type exclude: list[str]
|
:type exclude: list[str]
|
||||||
:type require: list[str]
|
:type require: list[str]
|
||||||
|
:type integration_targets: tuple[IntegrationTarget]
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if isinstance(args, TestConfig):
|
if isinstance(args, TestConfig):
|
||||||
|
@ -66,40 +76,42 @@ def delegate(args, exclude, require):
|
||||||
args.metadata.to_file(args.metadata_path)
|
args.metadata.to_file(args.metadata_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return delegate_command(args, exclude, require)
|
return delegate_command(args, exclude, require, integration_targets)
|
||||||
finally:
|
finally:
|
||||||
args.metadata_path = None
|
args.metadata_path = None
|
||||||
else:
|
else:
|
||||||
return delegate_command(args, exclude, require)
|
return delegate_command(args, exclude, require, integration_targets)
|
||||||
|
|
||||||
|
|
||||||
def delegate_command(args, exclude, require):
|
def delegate_command(args, exclude, require, integration_targets):
|
||||||
"""
|
"""
|
||||||
:type args: EnvironmentConfig
|
:type args: EnvironmentConfig
|
||||||
:type exclude: list[str]
|
:type exclude: list[str]
|
||||||
:type require: list[str]
|
:type require: list[str]
|
||||||
|
:type integration_targets: tuple[IntegrationTarget]
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if args.tox:
|
if args.tox:
|
||||||
delegate_tox(args, exclude, require)
|
delegate_tox(args, exclude, require, integration_targets)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if args.docker:
|
if args.docker:
|
||||||
delegate_docker(args, exclude, require)
|
delegate_docker(args, exclude, require, integration_targets)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if args.remote:
|
if args.remote:
|
||||||
delegate_remote(args, exclude, require)
|
delegate_remote(args, exclude, require, integration_targets)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def delegate_tox(args, exclude, require):
|
def delegate_tox(args, exclude, require, integration_targets):
|
||||||
"""
|
"""
|
||||||
:type args: EnvironmentConfig
|
:type args: EnvironmentConfig
|
||||||
:type exclude: list[str]
|
:type exclude: list[str]
|
||||||
:type require: list[str]
|
:type require: list[str]
|
||||||
|
:type integration_targets: tuple[IntegrationTarget]
|
||||||
"""
|
"""
|
||||||
if args.python:
|
if args.python:
|
||||||
versions = args.python_version,
|
versions = args.python_version,
|
||||||
|
@ -109,6 +121,12 @@ def delegate_tox(args, exclude, require):
|
||||||
else:
|
else:
|
||||||
versions = SUPPORTED_PYTHON_VERSIONS
|
versions = SUPPORTED_PYTHON_VERSIONS
|
||||||
|
|
||||||
|
if args.httptester:
|
||||||
|
needs_httptester = sorted(target.name for target in integration_targets if 'needs/httptester/' in target.aliases)
|
||||||
|
|
||||||
|
if needs_httptester:
|
||||||
|
display.warning('Use --docker or --remote to enable httptester for tests marked "needs/httptester": %s' % ', '.join(needs_httptester))
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'--tox': args.tox_args,
|
'--tox': args.tox_args,
|
||||||
'--tox-sitepackages': 0,
|
'--tox-sitepackages': 0,
|
||||||
|
@ -145,22 +163,27 @@ def delegate_tox(args, exclude, require):
|
||||||
run_command(args, tox + cmd, env=env)
|
run_command(args, tox + cmd, env=env)
|
||||||
|
|
||||||
|
|
||||||
def delegate_docker(args, exclude, require):
|
def delegate_docker(args, exclude, require, integration_targets):
|
||||||
"""
|
"""
|
||||||
:type args: EnvironmentConfig
|
:type args: EnvironmentConfig
|
||||||
:type exclude: list[str]
|
:type exclude: list[str]
|
||||||
:type require: list[str]
|
:type require: list[str]
|
||||||
|
:type integration_targets: tuple[IntegrationTarget]
|
||||||
"""
|
"""
|
||||||
util_image = args.docker_util
|
|
||||||
test_image = args.docker
|
test_image = args.docker
|
||||||
privileged = args.docker_privileged
|
privileged = args.docker_privileged
|
||||||
|
|
||||||
if util_image:
|
if isinstance(args, ShellConfig):
|
||||||
docker_pull(args, util_image)
|
use_httptester = args.httptester
|
||||||
|
else:
|
||||||
|
use_httptester = args.httptester and any('needs/httptester/' in target.aliases for target in integration_targets)
|
||||||
|
|
||||||
|
if use_httptester:
|
||||||
|
docker_pull(args, args.httptester)
|
||||||
|
|
||||||
docker_pull(args, test_image)
|
docker_pull(args, test_image)
|
||||||
|
|
||||||
util_id = None
|
httptester_id = None
|
||||||
test_id = None
|
test_id = None
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
|
@ -196,19 +219,10 @@ def delegate_docker(args, exclude, require):
|
||||||
|
|
||||||
lib.pytar.create_tarfile(local_source_fd.name, '.', tar_filter)
|
lib.pytar.create_tarfile(local_source_fd.name, '.', tar_filter)
|
||||||
|
|
||||||
if util_image:
|
if use_httptester:
|
||||||
util_options = [
|
httptester_id = run_httptester(args)
|
||||||
'--detach',
|
|
||||||
]
|
|
||||||
|
|
||||||
util_id, _ = docker_run(args, util_image, options=util_options)
|
|
||||||
|
|
||||||
if args.explain:
|
|
||||||
util_id = 'util_id'
|
|
||||||
else:
|
|
||||||
util_id = util_id.strip()
|
|
||||||
else:
|
else:
|
||||||
util_id = None
|
httptester_id = None
|
||||||
|
|
||||||
test_options = [
|
test_options = [
|
||||||
'--detach',
|
'--detach',
|
||||||
|
@ -227,14 +241,11 @@ def delegate_docker(args, exclude, require):
|
||||||
if os.path.exists(docker_socket):
|
if os.path.exists(docker_socket):
|
||||||
test_options += ['--volume', '%s:%s' % (docker_socket, docker_socket)]
|
test_options += ['--volume', '%s:%s' % (docker_socket, docker_socket)]
|
||||||
|
|
||||||
if util_id:
|
if httptester_id:
|
||||||
test_options += [
|
test_options += ['--env', 'HTTPTESTER=1']
|
||||||
'--link', '%s:ansible.http.tests' % util_id,
|
|
||||||
'--link', '%s:sni1.ansible.http.tests' % util_id,
|
for host in HTTPTESTER_HOSTS:
|
||||||
'--link', '%s:sni2.ansible.http.tests' % util_id,
|
test_options += ['--link', '%s:%s' % (httptester_id, host)]
|
||||||
'--link', '%s:fail.ansible.http.tests' % util_id,
|
|
||||||
'--env', 'HTTPTESTER=1',
|
|
||||||
]
|
|
||||||
|
|
||||||
if isinstance(args, IntegrationConfig):
|
if isinstance(args, IntegrationConfig):
|
||||||
cloud_platforms = get_cloud_providers(args)
|
cloud_platforms = get_cloud_providers(args)
|
||||||
|
@ -268,18 +279,19 @@ def delegate_docker(args, exclude, require):
|
||||||
docker_get(args, test_id, '/root/results.tgz', local_result_fd.name)
|
docker_get(args, test_id, '/root/results.tgz', local_result_fd.name)
|
||||||
run_command(args, ['tar', 'oxzf', local_result_fd.name, '-C', 'test'])
|
run_command(args, ['tar', 'oxzf', local_result_fd.name, '-C', 'test'])
|
||||||
finally:
|
finally:
|
||||||
if util_id:
|
if httptester_id:
|
||||||
docker_rm(args, util_id)
|
docker_rm(args, httptester_id)
|
||||||
|
|
||||||
if test_id:
|
if test_id:
|
||||||
docker_rm(args, test_id)
|
docker_rm(args, test_id)
|
||||||
|
|
||||||
|
|
||||||
def delegate_remote(args, exclude, require):
|
def delegate_remote(args, exclude, require, integration_targets):
|
||||||
"""
|
"""
|
||||||
:type args: EnvironmentConfig
|
:type args: EnvironmentConfig
|
||||||
:type exclude: list[str]
|
:type exclude: list[str]
|
||||||
:type require: list[str]
|
:type require: list[str]
|
||||||
|
:type integration_targets: tuple[IntegrationTarget]
|
||||||
"""
|
"""
|
||||||
parts = args.remote.split('/', 1)
|
parts = args.remote.split('/', 1)
|
||||||
|
|
||||||
|
@ -289,8 +301,24 @@ def delegate_remote(args, exclude, require):
|
||||||
core_ci = AnsibleCoreCI(args, platform, version, stage=args.remote_stage, provider=args.remote_provider)
|
core_ci = AnsibleCoreCI(args, platform, version, stage=args.remote_stage, provider=args.remote_provider)
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
|
if isinstance(args, ShellConfig):
|
||||||
|
use_httptester = args.httptester
|
||||||
|
else:
|
||||||
|
use_httptester = args.httptester and any('needs/httptester/' in target.aliases for target in integration_targets)
|
||||||
|
|
||||||
|
if use_httptester and not docker_available():
|
||||||
|
display.warning('Assuming --disable-httptester since `docker` is not available.')
|
||||||
|
use_httptester = False
|
||||||
|
|
||||||
|
httptester_id = None
|
||||||
|
ssh_options = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
core_ci.start()
|
core_ci.start()
|
||||||
|
|
||||||
|
if use_httptester:
|
||||||
|
httptester_id, ssh_options = start_httptester(args)
|
||||||
|
|
||||||
core_ci.wait()
|
core_ci.wait()
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
|
@ -299,6 +327,9 @@ def delegate_remote(args, exclude, require):
|
||||||
|
|
||||||
cmd = generate_command(args, 'ansible/test/runner/test.py', options, exclude, require)
|
cmd = generate_command(args, 'ansible/test/runner/test.py', options, exclude, require)
|
||||||
|
|
||||||
|
if httptester_id:
|
||||||
|
cmd += ['--inject-httptester']
|
||||||
|
|
||||||
if isinstance(args, TestConfig):
|
if isinstance(args, TestConfig):
|
||||||
if args.coverage and not args.coverage_label:
|
if args.coverage and not args.coverage_label:
|
||||||
cmd += ['--coverage-label', 'remote-%s-%s' % (platform, version)]
|
cmd += ['--coverage-label', 'remote-%s-%s' % (platform, version)]
|
||||||
|
@ -314,8 +345,6 @@ def delegate_remote(args, exclude, require):
|
||||||
manage = ManagePosixCI(core_ci)
|
manage = ManagePosixCI(core_ci)
|
||||||
manage.setup()
|
manage.setup()
|
||||||
|
|
||||||
ssh_options = []
|
|
||||||
|
|
||||||
if isinstance(args, IntegrationConfig):
|
if isinstance(args, IntegrationConfig):
|
||||||
cloud_platforms = get_cloud_providers(args)
|
cloud_platforms = get_cloud_providers(args)
|
||||||
|
|
||||||
|
@ -332,6 +361,9 @@ def delegate_remote(args, exclude, require):
|
||||||
if args.remote_terminate == 'always' or (args.remote_terminate == 'success' and success):
|
if args.remote_terminate == 'always' or (args.remote_terminate == 'success' and success):
|
||||||
core_ci.stop()
|
core_ci.stop()
|
||||||
|
|
||||||
|
if httptester_id:
|
||||||
|
docker_rm(args, httptester_id)
|
||||||
|
|
||||||
|
|
||||||
def generate_command(args, path, options, exclude, require):
|
def generate_command(args, path, options, exclude, require):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -15,6 +15,7 @@ from lib.util import (
|
||||||
run_command,
|
run_command,
|
||||||
common_environment,
|
common_environment,
|
||||||
display,
|
display,
|
||||||
|
find_executable,
|
||||||
)
|
)
|
||||||
|
|
||||||
from lib.config import (
|
from lib.config import (
|
||||||
|
@ -24,6 +25,13 @@ from lib.config import (
|
||||||
BUFFER_SIZE = 256 * 256
|
BUFFER_SIZE = 256 * 256
|
||||||
|
|
||||||
|
|
||||||
|
def docker_available():
|
||||||
|
"""
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return find_executable('docker', required=False)
|
||||||
|
|
||||||
|
|
||||||
def get_docker_container_id():
|
def get_docker_container_id():
|
||||||
"""
|
"""
|
||||||
:rtype: str | None
|
:rtype: str | None
|
||||||
|
@ -48,6 +56,17 @@ def get_docker_container_id():
|
||||||
raise ApplicationError('Found multiple container_id candidates: %s\n%s' % (sorted(container_ids), contents))
|
raise ApplicationError('Found multiple container_id candidates: %s\n%s' % (sorted(container_ids), contents))
|
||||||
|
|
||||||
|
|
||||||
|
def get_docker_container_ip(args, container_id):
|
||||||
|
"""
|
||||||
|
:type args: EnvironmentConfig
|
||||||
|
:type container_id: str
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
results = docker_inspect(args, container_id)
|
||||||
|
ipaddress = results[0]['NetworkSettings']['IPAddress']
|
||||||
|
return ipaddress
|
||||||
|
|
||||||
|
|
||||||
def docker_pull(args, image):
|
def docker_pull(args, image):
|
||||||
"""
|
"""
|
||||||
:type args: EnvironmentConfig
|
:type args: EnvironmentConfig
|
||||||
|
|
|
@ -48,6 +48,14 @@ from lib.util import (
|
||||||
find_executable,
|
find_executable,
|
||||||
raw_command,
|
raw_command,
|
||||||
get_coverage_path,
|
get_coverage_path,
|
||||||
|
get_available_port,
|
||||||
|
)
|
||||||
|
|
||||||
|
from lib.docker_util import (
|
||||||
|
docker_pull,
|
||||||
|
docker_run,
|
||||||
|
get_docker_container_id,
|
||||||
|
get_docker_container_ip,
|
||||||
)
|
)
|
||||||
|
|
||||||
from lib.ansible_util import (
|
from lib.ansible_util import (
|
||||||
|
@ -100,6 +108,12 @@ SUPPORTED_PYTHON_VERSIONS = (
|
||||||
'3.7',
|
'3.7',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
HTTPTESTER_HOSTS = (
|
||||||
|
'ansible.http.tests',
|
||||||
|
'sni1.ansible.http.tests',
|
||||||
|
'fail.ansible.http.tests',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_startup():
|
def check_startup():
|
||||||
"""Checks to perform at startup before running commands."""
|
"""Checks to perform at startup before running commands."""
|
||||||
|
@ -277,6 +291,9 @@ def command_shell(args):
|
||||||
|
|
||||||
install_command_requirements(args)
|
install_command_requirements(args)
|
||||||
|
|
||||||
|
if args.inject_httptester:
|
||||||
|
inject_httptester(args)
|
||||||
|
|
||||||
cmd = create_shell_command(['bash', '-i'])
|
cmd = create_shell_command(['bash', '-i'])
|
||||||
run_command(args, cmd)
|
run_command(args, cmd)
|
||||||
|
|
||||||
|
@ -649,7 +666,7 @@ def command_integration_filter(args, targets, init_callback=None):
|
||||||
cloud_init(args, internal_targets)
|
cloud_init(args, internal_targets)
|
||||||
|
|
||||||
if args.delegate:
|
if args.delegate:
|
||||||
raise Delegate(require=changes, exclude=exclude)
|
raise Delegate(require=changes, exclude=exclude, integration_targets=internal_targets)
|
||||||
|
|
||||||
install_command_requirements(args)
|
install_command_requirements(args)
|
||||||
|
|
||||||
|
@ -697,6 +714,9 @@ def command_integration_filtered(args, targets, all_targets):
|
||||||
display.warning('SSH service not responding. Waiting %d second(s) before checking again.' % seconds)
|
display.warning('SSH service not responding. Waiting %d second(s) before checking again.' % seconds)
|
||||||
time.sleep(seconds)
|
time.sleep(seconds)
|
||||||
|
|
||||||
|
if args.inject_httptester:
|
||||||
|
inject_httptester(args)
|
||||||
|
|
||||||
start_at_task = args.start_at_task
|
start_at_task = args.start_at_task
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
|
@ -815,6 +835,133 @@ def command_integration_filtered(args, targets, all_targets):
|
||||||
len(failed), len(passed) + len(failed), '\n'.join(target.name for target in failed)))
|
len(failed), len(passed) + len(failed), '\n'.join(target.name for target in failed)))
|
||||||
|
|
||||||
|
|
||||||
|
def start_httptester(args):
|
||||||
|
"""
|
||||||
|
:type args: EnvironmentConfig
|
||||||
|
:rtype: str, list[str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# map ports from remote -> localhost -> container
|
||||||
|
# passing through localhost is only used when ansible-test is not already running inside a docker container
|
||||||
|
ports = [
|
||||||
|
dict(
|
||||||
|
remote=8080,
|
||||||
|
container=80,
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
remote=8443,
|
||||||
|
container=443,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
container_id = get_docker_container_id()
|
||||||
|
|
||||||
|
if container_id:
|
||||||
|
display.info('Running in docker container: %s' % container_id, verbosity=1)
|
||||||
|
else:
|
||||||
|
for item in ports:
|
||||||
|
item['localhost'] = get_available_port()
|
||||||
|
|
||||||
|
docker_pull(args, args.httptester)
|
||||||
|
|
||||||
|
httptester_id = run_httptester(args, dict((port['localhost'], port['container']) for port in ports if 'localhost' in port))
|
||||||
|
|
||||||
|
if container_id:
|
||||||
|
container_host = get_docker_container_ip(args, httptester_id)
|
||||||
|
display.info('Found httptester container address: %s' % container_host, verbosity=1)
|
||||||
|
else:
|
||||||
|
container_host = 'localhost'
|
||||||
|
|
||||||
|
ssh_options = []
|
||||||
|
|
||||||
|
for port in ports:
|
||||||
|
ssh_options += ['-R', '%d:%s:%d' % (port['remote'], container_host, port.get('localhost', port['container']))]
|
||||||
|
|
||||||
|
return httptester_id, ssh_options
|
||||||
|
|
||||||
|
|
||||||
|
def run_httptester(args, ports=None):
|
||||||
|
"""
|
||||||
|
:type args: EnvironmentConfig
|
||||||
|
:type ports: dict[int, int] | None
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
options = [
|
||||||
|
'--detach',
|
||||||
|
]
|
||||||
|
|
||||||
|
if ports:
|
||||||
|
for localhost_port, container_port in ports.items():
|
||||||
|
options += ['-p', '%d:%d' % (localhost_port, container_port)]
|
||||||
|
|
||||||
|
httptester_id, _ = docker_run(args, args.httptester, options=options)
|
||||||
|
|
||||||
|
if args.explain:
|
||||||
|
httptester_id = 'httptester_id'
|
||||||
|
else:
|
||||||
|
httptester_id = httptester_id.strip()
|
||||||
|
|
||||||
|
return httptester_id
|
||||||
|
|
||||||
|
|
||||||
|
def inject_httptester(args):
|
||||||
|
"""
|
||||||
|
:type args: CommonConfig
|
||||||
|
"""
|
||||||
|
comment = ' # ansible-test httptester\n'
|
||||||
|
append_lines = ['127.0.0.1 %s%s' % (host, comment) for host in HTTPTESTER_HOSTS]
|
||||||
|
|
||||||
|
with open('/etc/hosts', 'r+') as hosts_fd:
|
||||||
|
original_lines = hosts_fd.readlines()
|
||||||
|
|
||||||
|
if not any(line.endswith(comment) for line in original_lines):
|
||||||
|
hosts_fd.writelines(append_lines)
|
||||||
|
|
||||||
|
# determine which forwarding mechanism to use
|
||||||
|
pfctl = find_executable('pfctl', required=False)
|
||||||
|
iptables = find_executable('iptables', required=False)
|
||||||
|
|
||||||
|
if pfctl:
|
||||||
|
kldload = find_executable('kldload', required=False)
|
||||||
|
|
||||||
|
if kldload:
|
||||||
|
try:
|
||||||
|
run_command(args, ['kldload', 'pf'], capture=True)
|
||||||
|
except SubprocessError:
|
||||||
|
pass # already loaded
|
||||||
|
|
||||||
|
rules = '''
|
||||||
|
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
|
||||||
|
rdr pass inet proto tcp from any to any port 443 -> 127.0.0.1 port 8443
|
||||||
|
'''
|
||||||
|
cmd = ['pfctl', '-ef', '-']
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_command(args, cmd, capture=True, data=rules)
|
||||||
|
except SubprocessError:
|
||||||
|
pass # non-zero exit status on success
|
||||||
|
|
||||||
|
elif iptables:
|
||||||
|
ports = [
|
||||||
|
(80, 8080),
|
||||||
|
(443, 8443),
|
||||||
|
]
|
||||||
|
|
||||||
|
for src, dst in ports:
|
||||||
|
rule = ['-o', 'lo', '-p', 'tcp', '--dport', str(src), '-j', 'REDIRECT', '--to-port', str(dst)]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# check for existing rule
|
||||||
|
cmd = ['iptables', '-t', 'nat', '-C', 'OUTPUT'] + rule
|
||||||
|
run_command(args, cmd, capture=True)
|
||||||
|
except SubprocessError:
|
||||||
|
# append rule when it does not exist
|
||||||
|
cmd = ['iptables', '-t', 'nat', '-A', 'OUTPUT'] + rule
|
||||||
|
run_command(args, cmd, capture=True)
|
||||||
|
else:
|
||||||
|
raise ApplicationError('No supported port forwarding mechanism detected.')
|
||||||
|
|
||||||
|
|
||||||
def run_setup_targets(args, test_dir, target_names, targets_dict, targets_executed, always):
|
def run_setup_targets(args, test_dir, target_names, targets_dict, targets_executed, always):
|
||||||
"""
|
"""
|
||||||
:type args: IntegrationConfig
|
:type args: IntegrationConfig
|
||||||
|
@ -852,6 +999,11 @@ def integration_environment(args, target, cmd):
|
||||||
"""
|
"""
|
||||||
env = ansible_environment(args)
|
env = ansible_environment(args)
|
||||||
|
|
||||||
|
if args.inject_httptester:
|
||||||
|
env.update(dict(
|
||||||
|
HTTPTESTER='1',
|
||||||
|
))
|
||||||
|
|
||||||
integration = dict(
|
integration = dict(
|
||||||
JUNIT_OUTPUT_DIR=os.path.abspath('test/results/junit'),
|
JUNIT_OUTPUT_DIR=os.path.abspath('test/results/junit'),
|
||||||
ANSIBLE_CALLBACK_WHITELIST='junit',
|
ANSIBLE_CALLBACK_WHITELIST='junit',
|
||||||
|
@ -1464,15 +1616,17 @@ class NoTestsForChanges(ApplicationWarning):
|
||||||
|
|
||||||
class Delegate(Exception):
|
class Delegate(Exception):
|
||||||
"""Trigger command delegation."""
|
"""Trigger command delegation."""
|
||||||
def __init__(self, exclude=None, require=None):
|
def __init__(self, exclude=None, require=None, integration_targets=None):
|
||||||
"""
|
"""
|
||||||
:type exclude: list[str] | None
|
:type exclude: list[str] | None
|
||||||
:type require: list[str] | None
|
:type require: list[str] | None
|
||||||
|
:type integration_targets: tuple[IntegrationTarget] | None
|
||||||
"""
|
"""
|
||||||
super(Delegate, self).__init__()
|
super(Delegate, self).__init__()
|
||||||
|
|
||||||
self.exclude = exclude or []
|
self.exclude = exclude or []
|
||||||
self.require = require or []
|
self.require = require or []
|
||||||
|
self.integration_targets = integration_targets or tuple()
|
||||||
|
|
||||||
|
|
||||||
class AllTargetsSkipped(ApplicationWarning):
|
class AllTargetsSkipped(ApplicationWarning):
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from __future__ import absolute_import, print_function
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
|
import contextlib
|
||||||
import errno
|
import errno
|
||||||
import filecmp
|
import filecmp
|
||||||
import fcntl
|
import fcntl
|
||||||
|
@ -14,6 +15,7 @@ import pkgutil
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
import stat
|
import stat
|
||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -720,6 +722,18 @@ def parse_to_dict(pattern, value):
|
||||||
return match.groupdict()
|
return match.groupdict()
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_port():
|
||||||
|
"""
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
# this relies on the kernel not reusing previously assigned ports immediately
|
||||||
|
socket_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
with contextlib.closing(socket_fd):
|
||||||
|
socket_fd.bind(('', 0))
|
||||||
|
return socket_fd.getsockname()[1]
|
||||||
|
|
||||||
|
|
||||||
def get_subclasses(class_type):
|
def get_subclasses(class_type):
|
||||||
"""
|
"""
|
||||||
:type class_type: type
|
:type class_type: type
|
||||||
|
|
|
@ -88,7 +88,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
args.func(config)
|
args.func(config)
|
||||||
except Delegate as ex:
|
except Delegate as ex:
|
||||||
delegate(config, ex.exclude, ex.require)
|
delegate(config, ex.exclude, ex.require, ex.integration_targets)
|
||||||
|
|
||||||
display.review_warnings()
|
display.review_warnings()
|
||||||
except ApplicationWarning as ex:
|
except ApplicationWarning as ex:
|
||||||
|
@ -278,6 +278,7 @@ def parse_args():
|
||||||
config=PosixIntegrationConfig)
|
config=PosixIntegrationConfig)
|
||||||
|
|
||||||
add_extra_docker_options(posix_integration)
|
add_extra_docker_options(posix_integration)
|
||||||
|
add_httptester_options(posix_integration, argparse)
|
||||||
|
|
||||||
network_integration = subparsers.add_parser('network-integration',
|
network_integration = subparsers.add_parser('network-integration',
|
||||||
parents=[integration],
|
parents=[integration],
|
||||||
|
@ -380,6 +381,7 @@ def parse_args():
|
||||||
|
|
||||||
add_environments(shell, tox_version=True)
|
add_environments(shell, tox_version=True)
|
||||||
add_extra_docker_options(shell)
|
add_extra_docker_options(shell)
|
||||||
|
add_httptester_options(shell, argparse)
|
||||||
|
|
||||||
coverage_common = argparse.ArgumentParser(add_help=False, parents=[common])
|
coverage_common = argparse.ArgumentParser(add_help=False, parents=[common])
|
||||||
|
|
||||||
|
@ -606,6 +608,29 @@ def add_extra_coverage_options(parser):
|
||||||
help='generate empty report of all python source files')
|
help='generate empty report of all python source files')
|
||||||
|
|
||||||
|
|
||||||
|
def add_httptester_options(parser, argparse):
|
||||||
|
"""
|
||||||
|
:type parser: argparse.ArgumentParser
|
||||||
|
:type argparse: argparse
|
||||||
|
"""
|
||||||
|
group = parser.add_mutually_exclusive_group()
|
||||||
|
|
||||||
|
group.add_argument('--httptester',
|
||||||
|
metavar='IMAGE',
|
||||||
|
default='quay.io/ansible/http-test-container:1.0.0',
|
||||||
|
help='docker image to use for the httptester container')
|
||||||
|
|
||||||
|
group.add_argument('--disable-httptester',
|
||||||
|
dest='httptester',
|
||||||
|
action='store_const',
|
||||||
|
const='',
|
||||||
|
help='do not use the httptester container')
|
||||||
|
|
||||||
|
parser.add_argument('--inject-httptester',
|
||||||
|
action='store_true',
|
||||||
|
help=argparse.SUPPRESS) # internal use only
|
||||||
|
|
||||||
|
|
||||||
def add_extra_docker_options(parser, integration=True):
|
def add_extra_docker_options(parser, integration=True):
|
||||||
"""
|
"""
|
||||||
:type parser: argparse.ArgumentParser
|
:type parser: argparse.ArgumentParser
|
||||||
|
@ -625,11 +650,6 @@ def add_extra_docker_options(parser, integration=True):
|
||||||
if not integration:
|
if not integration:
|
||||||
return
|
return
|
||||||
|
|
||||||
docker.add_argument('--docker-util',
|
|
||||||
metavar='IMAGE',
|
|
||||||
default='quay.io/ansible/http-test-container:1.0.0',
|
|
||||||
help='docker utility image to provide test services')
|
|
||||||
|
|
||||||
docker.add_argument('--docker-privileged',
|
docker.add_argument('--docker-privileged',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='run docker container in privileged mode')
|
help='run docker container in privileged mode')
|
||||||
|
|
Loading…
Reference in a new issue