| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Module containing class that implements an au_worker for GCE instances.""" |
| |
| from __future__ import print_function |
| |
| import datetime |
| import time |
| |
| from multiprocessing import Process |
| |
| from chromite.compute import gcloud |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import path_util |
| from crostestutils.au_test_harness import au_worker |
| from crostestutils.au_test_harness import constants |
| from crostestutils.au_test_harness import update_exception |
| |
| |
| class GCEAUWorker(au_worker.AUWorker): |
| """Test harness for updating GCE instances.""" |
| |
| _INSTANCE_PREFIX = 'test-instance-' |
| _IMAGE_PREFIX = 'test-image-' |
| |
| def __init__(self, options, test_results_root, project=constants.GCE_PROJECT, |
| zone=constants.GCE_ZONE): |
| """Processes GCE-specific options.""" |
| super(GCEAUWorker, self).__init__(options, test_results_root) |
| |
| # Google Cloud project and zone, in which to create the test instance. |
| self.gccontext = gcloud.GCContext(project, zone) |
| self.image = '' |
| self.instance = '' |
| self.instance_ip = '' |
| |
| # Background processes that delete throw-away instances. |
| self.bg_delete_processes = [] |
| |
| def CleanUp(self): |
| """Deletes throw-away instances and images""" |
| logging.info('Waiting for all instances and images to be deleted.') |
| |
| def _WaitForBackgroundDeleteProcesses(): |
| for p in self.bg_delete_processes: |
| p.join() |
| |
| _WaitForBackgroundDeleteProcesses() |
| # Delete the instance/image created by the last call to UpdateImage. |
| self._DeleteExistingInstanceInBackground() |
| _WaitForBackgroundDeleteProcesses() |
| logging.info('All instances/images are deleted.') |
| |
| def _DeleteExistingInstanceInBackground(self): |
| """Deletes existing instances if any.""" |
| |
| def _DeleteInstance(): |
| bg_delete = Process(target=self.gccontext.DeleteInstance, |
| args=(self.instance,), kwargs=dict(quiet=True)) |
| bg_delete.start() |
| self.bg_delete_processes.append(bg_delete) |
| self.instance = '' |
| self.instance_ip = '' |
| |
| def _DeleteImage(): |
| bg_delete = Process(target=self.gccontext.DeleteImage, |
| args=(self.image,), kwargs=dict(quiet=True)) |
| bg_delete.start() |
| self.bg_delete_processes.append(bg_delete) |
| self.image = '' |
| |
| if self.instance: |
| logging.info('Existing instance %s found. Deleting...', self.instance) |
| _DeleteInstance() |
| if self.image: |
| _DeleteImage() |
| |
| def PrepareBase(self, image_path, signed_base=False): |
| """Auto-update to base image to prepare for test.""" |
| return self.PrepareRealBase(image_path, signed_base) |
| |
| def UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| proxy_port=None, payload_signing_key=None): |
| """Updates the image on a GCE instance. |
| |
| Unlike real_au_worker, this method always creates a new instance. Note that |
| |image_path| has to be a valid Google Cloud Storage url, e.g., |
| gs://foo-bucket/bar.tar.gz. |
| """ |
| self._DeleteExistingInstanceInBackground() |
| ts = datetime.datetime.fromtimestamp(time.time()).strftime( |
| '%Y-%m-%d-%H-%M-%S') |
| image = '%s%s' % (self._IMAGE_PREFIX, ts) |
| instance = '%s%s' % (self._INSTANCE_PREFIX, ts) |
| |
| # Create an image from |image_path| and an instance from the image. |
| try: |
| self.gccontext.CreateImage(image, image_path) |
| self.gccontext.CreateInstance(instance, image) |
| except gcloud.GCCommandError as e: |
| raise update_exception.UpdateException( |
| 1, 'Update failed. Error: %s' % e.message) |
| self.instance_ip = self.gccontext.GetInstanceIP(instance) |
| self.instance = instance |
| self.image = image |
| |
| def VerifyImage(self, unittest, percent_required_to_pass=100, test=''): |
| """Verifies an image using test_that with verification suite.""" |
| test_directory, _ = self.GetNextResultsPath('autotest_tests') |
| if not test: |
| test = self.verify_suite |
| |
| self.TestInfo('Running test %s to verify image.' % test) |
| |
| cmd = ['test_that', '--no-quickmerge', '--results_dir=%s' % test_directory, |
| self.instance_ip, test] |
| if self.ssh_private_key is not None: |
| cmd.append('--ssh_private_key=%s' % |
| path_util.ToChrootPath(self.ssh_private_key)) |
| |
| result = cros_build_lib.RunCommand(cmd, error_code_ok=True, |
| enter_chroot=True, redirect_stdout=True, |
| cwd=constants.CROSUTILS_DIR) |
| ret = self.AssertEnoughTestsPassed(unittest, result.output, |
| percent_required_to_pass) |
| if not ret: |
| self._DeleteExistingInstanceInBackground() |
| |
| return ret |