From 47cfbd66054a8908fcf28a4bd829df635072a775 Mon Sep 17 00:00:00 2001 From: Jill R <4121322+jillr@users.noreply.github.com> Date: Tue, 4 Jun 2019 13:49:30 -0700 Subject: [PATCH] Add integration test provider for vmware (#55772) * Add integration test provider for vmware This change adds a new remote cloud provider option for vmware, supporting dynamic environments running on worldstream.nl as well as static environments specific by the user in a cloud-config template. --- .../cloud-config-vcenter.ini.template | 27 +++ test/runner/lib/cloud/vcenter.py | 154 +++++++++++++++--- test/runner/lib/core_ci.py | 8 + 3 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 test/integration/cloud-config-vcenter.ini.template diff --git a/test/integration/cloud-config-vcenter.ini.template b/test/integration/cloud-config-vcenter.ini.template new file mode 100644 index 00000000000..80b84aa2107 --- /dev/null +++ b/test/integration/cloud-config-vcenter.ini.template @@ -0,0 +1,27 @@ +# This is the configuration template for ansible-test VMware integration tests. +# +# You do not need this template if you are: +# +# 1) Running integration tests without using ansible-test. +# 2) Using the automatically provisioned VMware credentials in ansible-test. +# +# If you do not want to use the automatically provisioned temporary VMware credentials, +# fill in the @VAR placeholders below and save this file without the .template extension. +# This will cause ansible-test to use the given configuration instead of temporary credentials. +# +# NOTE: Automatic provisioning of VMware credentials requires an ansible-core-ci API key. + +[DEFAULT] +vcenter_username: @VMWARE_USERNAME +vcenter_password: @VMWARE_PASSWORD +vcenter_hostname: @VMWARE_HOSTNAME +vcenter_port: @VMWARE_PORT +vmware_validate_certs: @VMWARE_VALIDATE_CERTS +esxi1_username: @ESXI1_USERNAME +esxi1_hostname: @ESXI1_HOSTNAME +esxi1_https_port: @ESXI1_HTTPS_PORT +esxi1_password: @ESXI1_PASSWORD +esxi2_username: @ESXI2_USERNAME +esxi2_hostname: @ESXI2_HOSTNAME +esxi2_https_port: @ESXI2_HTTPS_PORT +esxi2_password: @ESXI2_PASSWORD diff --git a/test/runner/lib/cloud/vcenter.py b/test/runner/lib/cloud/vcenter.py index a21b26df609..a303086fa2f 100644 --- a/test/runner/lib/cloud/vcenter.py +++ b/test/runner/lib/cloud/vcenter.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, print_function import os +import time from lib.cloud import ( CloudProvider, @@ -12,6 +13,10 @@ from lib.cloud import ( from lib.util import ( find_executable, display, + ApplicationError, + is_shippable, + ConfigParser, + SubprocessError, ) from lib.docker_util import ( @@ -22,6 +27,14 @@ from lib.docker_util import ( get_docker_container_id, ) +from lib.core_ci import ( + AnsibleCoreCI, +) + +from lib.http import ( + HttpClient, +) + class VcenterProvider(CloudProvider): """VMware vcenter/esx plugin. Sets up cloud resources for tests.""" @@ -40,50 +53,83 @@ class VcenterProvider(CloudProvider): self.image = 'quay.io/ansible/vcenter-test-container:1.5.0' self.container_name = '' + # VMWare tests can be run on govcsim or baremetal, either BYO with a static config + # file or hosted in worldstream. Using an env var value of 'worldstream' with appropriate + # CI credentials will deploy a dynamic baremetal environment. The simulator is the default + # if no other config if provided. + self.vmware_test_platform = os.environ.get('VMWARE_TEST_PLATFORM', '') + self.aci = None + self.insecure = False + self.endpoint = '' + self.hostname = '' + self.port = 443 + 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] """ - docker = find_executable('docker', required=False) + if self.vmware_test_platform is None or 'govcsim': + docker = find_executable('docker', required=False) - if docker: - return + if docker: + return - skip = 'cloud/%s/' % self.platform - skipped = [target.name for target in targets if skip in target.aliases] + 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: %s' - % (skip.rstrip('/'), ', '.join(skipped))) + if skipped: + exclude.append(skip) + display.warning('Excluding tests marked "%s" which require the "docker" command: %s' + % (skip.rstrip('/'), ', '.join(skipped))) + else: + if os.path.isfile(self.config_static_path): + return + + aci = self._create_ansible_core_ci() + + if os.path.isfile(aci.ci_key): + return + + if is_shippable(): + return + + super(VcenterProvider, self).filter(targets, exclude) def setup(self): """Setup the cloud resource before delegation and register a cleanup callback.""" super(VcenterProvider, self).setup() + self._set_cloud_config('vmware_test_platform', self.vmware_test_platform) if self._use_static_config(): self._setup_static() + elif self.vmware_test_platform == 'worldstream': + self._setup_dynamic_baremetal() else: - self._setup_dynamic() + self._setup_dynamic_simulator() def get_docker_run_options(self): """Get any additional options needed when delegating tests to a docker container. :rtype: list[str] """ - if self.managed: + if self.managed and self.vmware_test_platform != 'worldstream': return ['--link', self.DOCKER_SIMULATOR_NAME] return [] def cleanup(self): """Clean up the cloud resource and any temporary configuration files after tests complete.""" + if self.vmware_test_platform == 'worldstream': + + if self.aci: + self.aci.stop() + if self.container_name: docker_rm(self.args, self.container_name) super(VcenterProvider, self).cleanup() - def _setup_dynamic(self): + def _setup_dynamic_simulator(self): """Create a vcenter simulator using docker.""" container_id = get_docker_container_id() @@ -139,8 +185,62 @@ class VcenterProvider(CloudProvider): ipaddress = results[0]['NetworkSettings']['IPAddress'] return ipaddress + def _setup_dynamic_baremetal(self): + """Request Esxi credentials through the Ansible Core CI service.""" + display.info('Provisioning %s cloud environment.' % self.platform, + verbosity=1) + + config = self._read_config_template() + + aci = self._create_ansible_core_ci() + + if not self.args.explain: + response = aci.start() + self.aci = aci + + config = self._populate_config_template(config, response) + self._write_config(config) + + def _create_ansible_core_ci(self): + """ + :rtype: AnsibleCoreCI + """ + return AnsibleCoreCI(self.args, 'vmware', 'vmware', + persist=False, stage=self.args.remote_stage, + provider='vmware') + def _setup_static(self): - raise NotImplementedError() + parser = ConfigParser() + parser.read(self.config_static_path) + + self.endpoint = parser.get('DEFAULT', 'vcenter_hostname') + self.port = parser.get('DEFAULT', 'vcenter_port') + + if parser.get('DEFAULT', 'vmware_validate_certs').lower() in ('no', 'false'): + self.insecure = True + + self._wait_for_service() + + def _wait_for_service(self): + """Wait for the VCenter service endpoint to accept connections.""" + if self.args.explain: + return + + client = HttpClient(self.args, always=True, insecure=self.insecure) + endpoint = 'https://%s:%s' % (self.endpoint, self.port) + + for i in range(1, 30): + display.info('Waiting for VCenter service: %s' % endpoint, verbosity=1) + + try: + client.get(endpoint) + return + except SubprocessError: + pass + + time.sleep(10) + + raise ApplicationError('Timeout waiting for VCenter service.') class VcenterEnvironment(CloudEnvironment): @@ -149,13 +249,29 @@ class VcenterEnvironment(CloudEnvironment): """ :rtype: CloudEnvironmentConfig """ - env_vars = dict( - VCENTER_HOST=self._get_cloud_config('vcenter_host'), - ) + vmware_test_platform = self._get_cloud_config('vmware_test_platform') + if vmware_test_platform == 'worldstream': + parser = ConfigParser() + parser.read(self.config_path) - ansible_vars = dict( - vcsim=self._get_cloud_config('vcenter_host'), - ) + # Most of the test cases use ansible_vars, but we plan to refactor these + # to use env_vars, output both for now + env_vars = dict( + (key.upper(), value) for key, value in parser.items('DEFAULT')) + + ansible_vars = dict( + resource_prefix=self.resource_prefix, + ) + ansible_vars.update(dict(parser.items('DEFAULT'))) + + else: + env_vars = dict( + VCENTER_HOST=self._get_cloud_config('vcenter_host'), + ) + + ansible_vars = dict( + vcsim=self._get_cloud_config('vcenter_host'), + ) return CloudEnvironmentConfig( env_vars=env_vars, diff --git a/test/runner/lib/core_ci.py b/test/runner/lib/core_ci.py index 277a673e212..7013199d76c 100644 --- a/test/runner/lib/core_ci.py +++ b/test/runner/lib/core_ci.py @@ -79,6 +79,9 @@ class AnsibleCoreCI(object): parallels=( 'osx', ), + vmware=( + 'vmware' + ), ) if provider: @@ -131,6 +134,11 @@ class AnsibleCoreCI(object): self.ssh_key = SshKey(args) self.port = None + elif self.provider == 'vmware': + self.ssh_key = SshKey(args) + self.endpoints = ['https://access.ws.testing.ansible.com'] + self.max_threshold = 1 + else: raise ApplicationError('Unsupported platform: %s' % platform)