mirror of https://github.com/ansible/ansible.git
Reorganize more ansible-test code. (#74611)
* Split out shell command. * Relocate ansible-test integration code.pull/74612/head
parent
065fc3ca17
commit
e6d7aecbe4
@ -0,0 +1,2 @@
|
||||
minor_changes:
|
||||
- ansible-test - Reorganize integration test implementation by command.
|
@ -0,0 +1,2 @@
|
||||
minor_changes:
|
||||
- ansible-test - Split out shell command implementation.
|
@ -0,0 +1,246 @@
|
||||
"""Network integration testing."""
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import time
|
||||
import textwrap
|
||||
import functools
|
||||
|
||||
from ... import types as t
|
||||
|
||||
from ...thread import (
|
||||
WrappedThread,
|
||||
)
|
||||
|
||||
from ...core_ci import (
|
||||
AnsibleCoreCI,
|
||||
SshKey,
|
||||
)
|
||||
|
||||
from ...manage_ci import (
|
||||
ManageNetworkCI,
|
||||
get_network_settings,
|
||||
)
|
||||
|
||||
from ...io import (
|
||||
write_text_file,
|
||||
)
|
||||
|
||||
from ...util import (
|
||||
ApplicationError,
|
||||
display,
|
||||
ANSIBLE_TEST_CONFIG_ROOT,
|
||||
)
|
||||
|
||||
from ...util_common import (
|
||||
get_python_path,
|
||||
handle_layout_messages,
|
||||
)
|
||||
|
||||
from ...target import (
|
||||
IntegrationTarget,
|
||||
walk_network_integration_targets,
|
||||
)
|
||||
|
||||
from ...config import (
|
||||
NetworkIntegrationConfig,
|
||||
)
|
||||
|
||||
from . import (
|
||||
command_integration_filter,
|
||||
command_integration_filtered,
|
||||
get_inventory_relative_path,
|
||||
check_inventory,
|
||||
delegate_inventory,
|
||||
)
|
||||
|
||||
from ...data import (
|
||||
data_context,
|
||||
)
|
||||
|
||||
|
||||
def command_network_integration(args):
|
||||
"""
|
||||
:type args: NetworkIntegrationConfig
|
||||
"""
|
||||
handle_layout_messages(data_context().content.integration_messages)
|
||||
|
||||
inventory_relative_path = get_inventory_relative_path(args)
|
||||
template_path = os.path.join(ANSIBLE_TEST_CONFIG_ROOT, os.path.basename(inventory_relative_path)) + '.template'
|
||||
|
||||
if args.inventory:
|
||||
inventory_path = os.path.join(data_context().content.root, data_context().content.integration_path, args.inventory)
|
||||
else:
|
||||
inventory_path = os.path.join(data_context().content.root, inventory_relative_path)
|
||||
|
||||
if args.no_temp_workdir:
|
||||
# temporary solution to keep DCI tests working
|
||||
inventory_exists = os.path.exists(inventory_path)
|
||||
else:
|
||||
inventory_exists = os.path.isfile(inventory_path)
|
||||
|
||||
if not args.explain and not args.platform and not inventory_exists:
|
||||
raise ApplicationError(
|
||||
'Inventory not found: %s\n'
|
||||
'Use --inventory to specify the inventory path.\n'
|
||||
'Use --platform to provision resources and generate an inventory file.\n'
|
||||
'See also inventory template: %s' % (inventory_path, template_path)
|
||||
)
|
||||
|
||||
check_inventory(args, inventory_path)
|
||||
delegate_inventory(args, inventory_path)
|
||||
|
||||
all_targets = tuple(walk_network_integration_targets(include_hidden=True))
|
||||
internal_targets = command_integration_filter(args, all_targets, init_callback=network_init)
|
||||
instances = [] # type: t.List[WrappedThread]
|
||||
|
||||
if args.platform:
|
||||
get_python_path(args, args.python_executable) # initialize before starting threads
|
||||
|
||||
configs = dict((config['platform_version'], config) for config in args.metadata.instance_config)
|
||||
|
||||
for platform_version in args.platform:
|
||||
platform, version = platform_version.split('/', 1)
|
||||
config = configs.get(platform_version)
|
||||
|
||||
if not config:
|
||||
continue
|
||||
|
||||
instance = WrappedThread(functools.partial(network_run, args, platform, version, config))
|
||||
instance.daemon = True
|
||||
instance.start()
|
||||
instances.append(instance)
|
||||
|
||||
while any(instance.is_alive() for instance in instances):
|
||||
time.sleep(1)
|
||||
|
||||
remotes = [instance.wait_for_result() for instance in instances]
|
||||
inventory = network_inventory(args, remotes)
|
||||
|
||||
display.info('>>> Inventory: %s\n%s' % (inventory_path, inventory.strip()), verbosity=3)
|
||||
|
||||
if not args.explain:
|
||||
write_text_file(inventory_path, inventory)
|
||||
|
||||
success = False
|
||||
|
||||
try:
|
||||
command_integration_filtered(args, internal_targets, all_targets, inventory_path)
|
||||
success = True
|
||||
finally:
|
||||
if args.remote_terminate == 'always' or (args.remote_terminate == 'success' and success):
|
||||
for instance in instances:
|
||||
instance.result.stop()
|
||||
|
||||
|
||||
def network_init(args, internal_targets): # type: (NetworkIntegrationConfig, t.Tuple[IntegrationTarget, ...]) -> None
|
||||
"""Initialize platforms for network integration tests."""
|
||||
if not args.platform:
|
||||
return
|
||||
|
||||
if args.metadata.instance_config is not None:
|
||||
return
|
||||
|
||||
platform_targets = set(a for target in internal_targets for a in target.aliases if a.startswith('network/'))
|
||||
|
||||
instances = [] # type: t.List[WrappedThread]
|
||||
|
||||
# generate an ssh key (if needed) up front once, instead of for each instance
|
||||
SshKey(args)
|
||||
|
||||
for platform_version in args.platform:
|
||||
platform, version = platform_version.split('/', 1)
|
||||
platform_target = 'network/%s/' % platform
|
||||
|
||||
if platform_target not in platform_targets:
|
||||
display.warning('Skipping "%s" because selected tests do not target the "%s" platform.' % (
|
||||
platform_version, platform))
|
||||
continue
|
||||
|
||||
instance = WrappedThread(functools.partial(network_start, args, platform, version))
|
||||
instance.daemon = True
|
||||
instance.start()
|
||||
instances.append(instance)
|
||||
|
||||
while any(instance.is_alive() for instance in instances):
|
||||
time.sleep(1)
|
||||
|
||||
args.metadata.instance_config = [instance.wait_for_result() for instance in instances]
|
||||
|
||||
|
||||
def network_start(args, platform, version):
|
||||
"""
|
||||
:type args: NetworkIntegrationConfig
|
||||
:type platform: str
|
||||
:type version: str
|
||||
:rtype: AnsibleCoreCI
|
||||
"""
|
||||
core_ci = AnsibleCoreCI(args, platform, version, stage=args.remote_stage, provider=args.remote_provider)
|
||||
core_ci.start()
|
||||
|
||||
return core_ci.save()
|
||||
|
||||
|
||||
def network_run(args, platform, version, config):
|
||||
"""
|
||||
:type args: NetworkIntegrationConfig
|
||||
:type platform: str
|
||||
:type version: str
|
||||
:type config: dict[str, str]
|
||||
:rtype: AnsibleCoreCI
|
||||
"""
|
||||
core_ci = AnsibleCoreCI(args, platform, version, stage=args.remote_stage, provider=args.remote_provider, load=False)
|
||||
core_ci.load(config)
|
||||
core_ci.wait()
|
||||
|
||||
manage = ManageNetworkCI(args, core_ci)
|
||||
manage.wait()
|
||||
|
||||
return core_ci
|
||||
|
||||
|
||||
def network_inventory(args, remotes):
|
||||
"""
|
||||
:type args: NetworkIntegrationConfig
|
||||
:type remotes: list[AnsibleCoreCI]
|
||||
:rtype: str
|
||||
"""
|
||||
groups = dict([(remote.platform, []) for remote in remotes])
|
||||
net = []
|
||||
|
||||
for remote in remotes:
|
||||
options = dict(
|
||||
ansible_host=remote.connection.hostname,
|
||||
ansible_user=remote.connection.username,
|
||||
ansible_ssh_private_key_file=os.path.abspath(remote.ssh_key.key),
|
||||
)
|
||||
|
||||
settings = get_network_settings(args, remote.platform, remote.version)
|
||||
|
||||
options.update(settings.inventory_vars)
|
||||
|
||||
groups[remote.platform].append(
|
||||
'%s %s' % (
|
||||
remote.name.replace('.', '-'),
|
||||
' '.join('%s="%s"' % (k, options[k]) for k in sorted(options)),
|
||||
)
|
||||
)
|
||||
|
||||
net.append(remote.platform)
|
||||
|
||||
groups['net:children'] = net
|
||||
|
||||
template = ''
|
||||
|
||||
for group in groups:
|
||||
hosts = '\n'.join(groups[group])
|
||||
|
||||
template += textwrap.dedent("""
|
||||
[%s]
|
||||
%s
|
||||
""") % (group, hosts)
|
||||
|
||||
inventory = template
|
||||
|
||||
return inventory
|
@ -0,0 +1,57 @@
|
||||
"""POSIX integration testing."""
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
from ... import types as t
|
||||
|
||||
from ...util import (
|
||||
ANSIBLE_TEST_DATA_ROOT,
|
||||
)
|
||||
|
||||
from ...util_common import (
|
||||
handle_layout_messages,
|
||||
)
|
||||
|
||||
from ...containers import (
|
||||
SshConnectionDetail,
|
||||
create_container_hooks,
|
||||
)
|
||||
|
||||
from ...target import (
|
||||
walk_posix_integration_targets,
|
||||
)
|
||||
|
||||
from ...config import (
|
||||
PosixIntegrationConfig,
|
||||
)
|
||||
|
||||
from . import (
|
||||
command_integration_filter,
|
||||
command_integration_filtered,
|
||||
get_inventory_relative_path,
|
||||
)
|
||||
|
||||
from ...data import (
|
||||
data_context,
|
||||
)
|
||||
|
||||
|
||||
def command_posix_integration(args):
|
||||
"""
|
||||
:type args: PosixIntegrationConfig
|
||||
"""
|
||||
handle_layout_messages(data_context().content.integration_messages)
|
||||
|
||||
inventory_relative_path = get_inventory_relative_path(args)
|
||||
inventory_path = os.path.join(ANSIBLE_TEST_DATA_ROOT, os.path.basename(inventory_relative_path))
|
||||
|
||||
all_targets = tuple(walk_posix_integration_targets(include_hidden=True))
|
||||
internal_targets = command_integration_filter(args, all_targets)
|
||||
|
||||
managed_connections = None # type: t.Optional[t.List[SshConnectionDetail]]
|
||||
|
||||
pre_target, post_target = create_container_hooks(args, managed_connections)
|
||||
|
||||
command_integration_filtered(args, internal_targets, all_targets, inventory_path, pre_target=pre_target, post_target=post_target)
|
@ -0,0 +1,310 @@
|
||||
"""Windows integration testing."""
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import time
|
||||
import textwrap
|
||||
import functools
|
||||
|
||||
from ... import types as t
|
||||
|
||||
from ...thread import (
|
||||
WrappedThread,
|
||||
)
|
||||
|
||||
from ...core_ci import (
|
||||
AnsibleCoreCI,
|
||||
SshKey,
|
||||
)
|
||||
|
||||
from ...manage_ci import (
|
||||
ManageWindowsCI,
|
||||
)
|
||||
|
||||
from ...io import (
|
||||
write_text_file,
|
||||
)
|
||||
|
||||
from ...util import (
|
||||
ApplicationError,
|
||||
display,
|
||||
ANSIBLE_TEST_CONFIG_ROOT,
|
||||
tempdir,
|
||||
open_zipfile,
|
||||
)
|
||||
|
||||
from ...util_common import (
|
||||
get_python_path,
|
||||
ResultType,
|
||||
handle_layout_messages,
|
||||
)
|
||||
|
||||
from ...containers import (
|
||||
SshConnectionDetail,
|
||||
create_container_hooks,
|
||||
)
|
||||
|
||||
from ...ansible_util import (
|
||||
run_playbook,
|
||||
)
|
||||
|
||||
from ...target import (
|
||||
IntegrationTarget,
|
||||
walk_windows_integration_targets,
|
||||
)
|
||||
|
||||
from ...config import (
|
||||
WindowsIntegrationConfig,
|
||||
)
|
||||
|
||||
from . import (
|
||||
command_integration_filter,
|
||||
command_integration_filtered,
|
||||
get_inventory_relative_path,
|
||||
check_inventory,
|
||||
delegate_inventory,
|
||||
)
|
||||
|
||||
from ...data import (
|
||||
data_context,
|
||||
)
|
||||
|
||||
from ...executor import (
|
||||
parse_inventory,
|
||||
get_hosts,
|
||||
)
|
||||
|
||||
|
||||
def command_windows_integration(args):
|
||||
"""
|
||||
:type args: WindowsIntegrationConfig
|
||||
"""
|
||||
handle_layout_messages(data_context().content.integration_messages)
|
||||
|
||||
inventory_relative_path = get_inventory_relative_path(args)
|
||||
template_path = os.path.join(ANSIBLE_TEST_CONFIG_ROOT, os.path.basename(inventory_relative_path)) + '.template'
|
||||
|
||||
if args.inventory:
|
||||
inventory_path = os.path.join(data_context().content.root, data_context().content.integration_path, args.inventory)
|
||||
else:
|
||||
inventory_path = os.path.join(data_context().content.root, inventory_relative_path)
|
||||
|
||||
if not args.explain and not args.windows and not os.path.isfile(inventory_path):
|
||||
raise ApplicationError(
|
||||
'Inventory not found: %s\n'
|
||||
'Use --inventory to specify the inventory path.\n'
|
||||
'Use --windows to provision resources and generate an inventory file.\n'
|
||||
'See also inventory template: %s' % (inventory_path, template_path)
|
||||
)
|
||||
|
||||
check_inventory(args, inventory_path)
|
||||
delegate_inventory(args, inventory_path)
|
||||
|
||||
all_targets = tuple(walk_windows_integration_targets(include_hidden=True))
|
||||
internal_targets = command_integration_filter(args, all_targets, init_callback=windows_init)
|
||||
instances = [] # type: t.List[WrappedThread]
|
||||
managed_connections = [] # type: t.List[SshConnectionDetail]
|
||||
|
||||
if args.windows:
|
||||
get_python_path(args, args.python_executable) # initialize before starting threads
|
||||
|
||||
configs = dict((config['platform_version'], config) for config in args.metadata.instance_config)
|
||||
|
||||
for version in args.windows:
|
||||
config = configs['windows/%s' % version]
|
||||
|
||||
instance = WrappedThread(functools.partial(windows_run, args, version, config))
|
||||
instance.daemon = True
|
||||
instance.start()
|
||||
instances.append(instance)
|
||||
|
||||
while any(instance.is_alive() for instance in instances):
|
||||
time.sleep(1)
|
||||
|
||||
remotes = [instance.wait_for_result() for instance in instances]
|
||||
inventory = windows_inventory(remotes)
|
||||
|
||||
display.info('>>> Inventory: %s\n%s' % (inventory_path, inventory.strip()), verbosity=3)
|
||||
|
||||
if not args.explain:
|
||||
write_text_file(inventory_path, inventory)
|
||||
|
||||
for core_ci in remotes:
|
||||
ssh_con = core_ci.connection
|
||||
ssh = SshConnectionDetail(core_ci.name, ssh_con.hostname, 22, ssh_con.username, core_ci.ssh_key.key, shell_type='powershell')
|
||||
managed_connections.append(ssh)
|
||||
elif args.explain:
|
||||
identity_file = SshKey(args).key
|
||||
|
||||
# mock connection details to prevent tracebacks in explain mode
|
||||
managed_connections = [SshConnectionDetail(
|
||||
name='windows',
|
||||
host='windows',
|
||||
port=22,
|
||||
user='administrator',
|
||||
identity_file=identity_file,
|
||||
shell_type='powershell',
|
||||
)]
|
||||
else:
|
||||
inventory = parse_inventory(args, inventory_path)
|
||||
hosts = get_hosts(inventory, 'windows')
|
||||
identity_file = SshKey(args).key
|
||||
|
||||
managed_connections = [SshConnectionDetail(
|
||||
name=name,
|
||||
host=config['ansible_host'],
|
||||
port=22,
|
||||
user=config['ansible_user'],
|
||||
identity_file=identity_file,
|
||||
shell_type='powershell',
|
||||
) for name, config in hosts.items()]
|
||||
|
||||
if managed_connections:
|
||||
display.info('Generated SSH connection details from inventory:\n%s' % (
|
||||
'\n'.join('%s %s@%s:%d' % (ssh.name, ssh.user, ssh.host, ssh.port) for ssh in managed_connections)), verbosity=1)
|
||||
|
||||
pre_target, post_target = create_container_hooks(args, managed_connections)
|
||||
|
||||
remote_temp_path = None
|
||||
|
||||
if args.coverage and not args.coverage_check:
|
||||
# Create the remote directory that is writable by everyone. Use Ansible to talk to the remote host.
|
||||
remote_temp_path = 'C:\\ansible_test_coverage_%s' % time.time()
|
||||
playbook_vars = {'remote_temp_path': remote_temp_path}
|
||||
run_playbook(args, inventory_path, 'windows_coverage_setup.yml', playbook_vars)
|
||||
|
||||
success = False
|
||||
|
||||
try:
|
||||
command_integration_filtered(args, internal_targets, all_targets, inventory_path, pre_target=pre_target,
|
||||
post_target=post_target, remote_temp_path=remote_temp_path)
|
||||
success = True
|
||||
finally:
|
||||
if remote_temp_path:
|
||||
# Zip up the coverage files that were generated and fetch it back to localhost.
|
||||
with tempdir() as local_temp_path:
|
||||
playbook_vars = {'remote_temp_path': remote_temp_path, 'local_temp_path': local_temp_path}
|
||||
run_playbook(args, inventory_path, 'windows_coverage_teardown.yml', playbook_vars)
|
||||
|
||||
for filename in os.listdir(local_temp_path):
|
||||
with open_zipfile(os.path.join(local_temp_path, filename)) as coverage_zip:
|
||||
coverage_zip.extractall(ResultType.COVERAGE.path)
|
||||
|
||||
if args.remote_terminate == 'always' or (args.remote_terminate == 'success' and success):
|
||||
for instance in instances:
|
||||
instance.result.stop()
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def windows_init(args, internal_targets): # pylint: disable=locally-disabled, unused-argument
|
||||
"""
|
||||
:type args: WindowsIntegrationConfig
|
||||
:type internal_targets: tuple[IntegrationTarget]
|
||||
"""
|
||||
# generate an ssh key (if needed) up front once, instead of for each instance
|
||||
SshKey(args)
|
||||
|
||||
if not args.windows:
|
||||
return
|
||||
|
||||
if args.metadata.instance_config is not None:
|
||||
return
|
||||
|
||||
instances = [] # type: t.List[WrappedThread]
|
||||
|
||||
for version in args.windows:
|
||||
instance = WrappedThread(functools.partial(windows_start, args, version))
|
||||
instance.daemon = True
|
||||
instance.start()
|
||||
instances.append(instance)
|
||||
|
||||
while any(instance.is_alive() for instance in instances):
|
||||
time.sleep(1)
|
||||
|
||||
args.metadata.instance_config = [instance.wait_for_result() for instance in instances]
|
||||
|
||||
|
||||
def windows_start(args, version):
|
||||
"""
|
||||
:type args: WindowsIntegrationConfig
|
||||
:type version: str
|
||||
:rtype: AnsibleCoreCI
|
||||
"""
|
||||
core_ci = AnsibleCoreCI(args, 'windows', version, stage=args.remote_stage, provider=args.remote_provider)
|
||||
core_ci.start()
|
||||
|
||||
return core_ci.save()
|
||||
|
||||
|
||||
def windows_run(args, version, config):
|
||||
"""
|
||||
:type args: WindowsIntegrationConfig
|
||||
:type version: str
|
||||
:type config: dict[str, str]
|
||||
:rtype: AnsibleCoreCI
|
||||
"""
|
||||
core_ci = AnsibleCoreCI(args, 'windows', version, stage=args.remote_stage, provider=args.remote_provider, load=False)
|
||||
core_ci.load(config)
|
||||
core_ci.wait()
|
||||
|
||||
manage = ManageWindowsCI(core_ci)
|
||||
manage.wait()
|
||||
|
||||
return core_ci
|
||||
|
||||
|
||||
def windows_inventory(remotes):
|
||||
"""
|
||||
:type remotes: list[AnsibleCoreCI]
|
||||
:rtype: str
|
||||
"""
|
||||
hosts = []
|
||||
|
||||
for remote in remotes:
|
||||
options = dict(
|
||||
ansible_host=remote.connection.hostname,
|
||||
ansible_user=remote.connection.username,
|
||||
ansible_password=remote.connection.password,
|
||||
ansible_port=remote.connection.port,
|
||||
)
|
||||
|
||||
# used for the connection_windows_ssh test target
|
||||
if remote.ssh_key:
|
||||
options["ansible_ssh_private_key_file"] = os.path.abspath(remote.ssh_key.key)
|
||||
|
||||
if remote.name == 'windows-2016':
|
||||
options.update(
|
||||
# force 2016 to use NTLM + HTTP message encryption
|
||||
ansible_connection='winrm',
|
||||
ansible_winrm_server_cert_validation='ignore',
|
||||
ansible_winrm_transport='ntlm',
|
||||
ansible_winrm_scheme='http',
|
||||
ansible_port='5985',
|
||||
)
|
||||
else:
|
||||
options.update(
|
||||
ansible_connection='winrm',
|
||||
ansible_winrm_server_cert_validation='ignore',
|
||||
)
|
||||
|
||||
hosts.append(
|
||||
'%s %s' % (
|
||||
remote.name.replace('/', '_'),
|
||||
' '.join('%s="%s"' % (k, options[k]) for k in sorted(options)),
|
||||
)
|
||||
)
|
||||
|
||||
template = """
|
||||
[windows]
|
||||
%s
|
||||
|
||||
# support winrm binary module tests (temporary solution)
|
||||
[testhost:children]
|
||||
windows
|
||||
"""
|
||||
|
||||
template = textwrap.dedent(template)
|
||||
inventory = template % ('\n'.join(hosts))
|
||||
|
||||
return inventory
|
@ -0,0 +1,30 @@
|
||||
"""Open a shell prompt inside an ansible-test environment."""
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ...util_common import (
|
||||
run_command,
|
||||
)
|
||||
|
||||
from ...config import (
|
||||
ShellConfig,
|
||||
)
|
||||
|
||||
from ...executor import (
|
||||
Delegate,
|
||||
create_shell_command,
|
||||
install_command_requirements,
|
||||
)
|
||||
|
||||
|
||||
def command_shell(args):
|
||||
"""
|
||||
:type args: ShellConfig
|
||||
"""
|
||||
if args.delegate:
|
||||
raise Delegate()
|
||||
|
||||
install_command_requirements(args)
|
||||
|
||||
cmd = create_shell_command(['bash', '-i'])
|
||||
run_command(args, cmd)
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue