mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
9.3 KiB
Python
311 lines
9.3 KiB
Python
"""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
|