Index: bin/cros_au_test_harness.py |
diff --git a/bin/cros_au_test_harness.py b/bin/cros_au_test_harness.py |
index 67c0485ba08ea58e74a81a726eed19ff22ac7639..703e9736bf2f5b92b3aab4c211f701c4c112c654 100755 |
--- a/bin/cros_au_test_harness.py |
+++ b/bin/cros_au_test_harness.py |
@@ -15,8 +15,10 @@ |
import optparse |
import os |
import re |
+import shutil |
import subprocess |
import sys |
+import tempfile |
import threading |
import time |
import unittest |
@@ -53,11 +55,74 @@ class AUTest(object): |
self.crosutils = os.path.join(os.path.dirname(__file__), '..') |
self.crosutilsbin = os.path.join(os.path.dirname(__file__)) |
self.download_folder = os.path.join(self.crosutils, 'latest_download') |
+ self.vm_image_path = None |
if not os.path.exists(self.download_folder): |
os.makedirs(self.download_folder) |
# -------- Helper functions --------- |
+ def _PrepareRealBase(self, image_path): |
sosa
2011/02/10 06:14:35
These two are moved out so we can call them in the
|
+ self.PerformUpdate(image_path) |
+ |
+ def _PrepareVMBase(self, image_path): |
+ # VM Constants. |
+ FULL_VDISK_SIZE = 6072 |
+ FULL_STATEFULFS_SIZE = 3074 |
+ # Needed for VM delta updates. We need to use the qemu image rather |
+ # than the base image on a first update. By tracking the first_update |
+ # we can set src_image to the qemu form of the base image when |
+ # performing generating the delta payload. |
+ self._first_update = True |
+ self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( |
+ image_path) |
+ if not os.path.exists(self.vm_image_path): |
+ Info('Creating %s' % self.vm_image_path) |
+ RunCommand(['%s/image_to_vm.sh' % self.crosutils, |
+ '--full', |
+ '--from=%s' % ReinterpretPathForChroot( |
+ os.path.dirname(image_path)), |
+ '--vdisk_size=%s' % FULL_VDISK_SIZE, |
+ '--statefulfs_size=%s' % FULL_STATEFULFS_SIZE, |
+ '--board=%s' % self.board, |
+ '--test_image'], enter_chroot=True) |
+ |
+ Info('Using %s as base' % self.vm_image_path) |
+ self.assertTrue(os.path.exists(self.vm_image_path)) |
+ |
+ def AppendUpdateFlags(self, cmd, image_path, src_image_path, proxy_port, |
+ private_key_path): |
+ """Appends common args to an update cmd defined by an array. |
+ |
+ Modifies cmd in places by appending appropriate items given args. |
+ """ |
+ if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) |
+ |
+ # Get pregenerated update if we have one. |
+ update_id = _GenerateUpdateId(target=image_path, src=src_image_path, |
+ key=private_key_path) |
+ cache_path = dev_server_cache[update_id] |
+ if cache_path: |
+ update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) |
+ cmd.append('--update_url=%s' % update_url) |
+ else: |
+ cmd.append('--image=%s' % image_path) |
+ if src_image_path: cmd.append('--src_image=%s' % src_image_path) |
+ |
+ def RunUpdateCmd(self, cmd): |
+ """Runs the given update cmd given verbose options. |
+ |
+ Raises an UpdateException if the update fails. |
+ """ |
+ if self.verbose: |
+ try: |
+ RunCommand(cmd) |
+ except Exception, e: |
+ raise UpdateException(1, e.message) |
+ else: |
+ (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
+ if code != 0: |
+ raise UpdateException(code, stdout) |
+ |
def GetStatefulChangeFlag(self, stateful_change): |
"""Returns the flag to pass to image_to_vm for the stateful change.""" |
stateful_change_flag = '' |
@@ -101,7 +166,7 @@ class AUTest(object): |
return percent_passed |
def PerformUpdate(self, image_path, src_image_path='', stateful_change='old', |
- proxy_port=None): |
+ proxy_port=None, private_key_path=None): |
"""Performs an update using _UpdateImage and reports any error. |
Subclasses should not override this method but override _UpdateImage |
@@ -121,10 +186,14 @@ class AUTest(object): |
Raises an UpdateException if _UpdateImage returns an error. |
""" |
try: |
- if not self.use_delta_updates: |
- src_image_path = '' |
+ if not self.use_delta_updates: src_image_path = '' |
+ if private_key_path: |
+ key_to_use = private_key_path |
sosa
2011/02/10 06:14:35
Allows you to override so you can specify a bad ke
|
+ else: |
+ key_to_use = self.private_key |
- self._UpdateImage(image_path, src_image_path, stateful_change, proxy_port) |
+ self._UpdateImage(image_path, src_image_path, stateful_change, proxy_port, |
+ key_to_use) |
except UpdateException as err: |
# If the update fails, print it out |
Warning(err.stdout) |
@@ -178,6 +247,8 @@ class AUTest(object): |
cls.base_image_path = options.base_image |
cls.target_image_path = options.target_image |
cls.use_delta_updates = options.delta |
+ cls.board = options.board |
+ cls.private_key = options.private_key |
if options.quick_test: |
cls.verify_suite = 'build_RootFilesystemSize' |
else: |
@@ -199,7 +270,7 @@ class AUTest(object): |
pass |
def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
- proxy_port=None): |
+ proxy_port=None, private_key_path=None): |
"""Implementation of an actual update. |
See PerformUpdate for description of args. Subclasses must override this |
@@ -410,32 +481,20 @@ class RealAUTest(unittest.TestCase, AUTest): |
def PrepareBase(self, image_path): |
"""Auto-update to base image to prepare for test.""" |
- self.PerformUpdate(image_path) |
+ _PrepareRealBase(image_path) |
def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
- proxy_port=None): |
+ proxy_port=None, private_key_path=None): |
"""Updates a remote image using image_to_live.sh.""" |
stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
cmd = ['%s/image_to_live.sh' % self.crosutils, |
- '--image=%s' % image_path, |
'--remote=%s' % self.remote, |
stateful_change_flag, |
'--verify', |
- '--src_image=%s' % src_image_path |
] |
- |
- if proxy_port: |
- cmd.append('--proxy_port=%s' % proxy_port) |
- |
- if self.verbose: |
- try: |
- RunCommand(cmd) |
- except Exception, e: |
- raise UpdateException(1, e.message) |
- else: |
- (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
- if code != 0: |
- raise UpdateException(code, stdout) |
+ self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port, |
+ private_key_path) |
+ self.RunUpdateCmd(cmd) |
def _UpdateUsingPayload(self, update_path, stateful_change='old', |
proxy_port=None): |
@@ -447,19 +506,8 @@ class RealAUTest(unittest.TestCase, AUTest): |
stateful_change_flag, |
'--verify', |
] |
- |
- if proxy_port: |
- cmd.append('--proxy_port=%s' % proxy_port) |
- |
- if self.verbose: |
- try: |
- RunCommand(cmd) |
- except Exception, e: |
- raise UpdateException(1, e.message) |
- else: |
- (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
- if code != 0: |
- raise UpdateException(code, stdout) |
+ if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) |
+ self.RunUpdateCmd(cmd) |
def VerifyImage(self, percent_required_to_pass): |
"""Verifies an image using run_remote_tests.sh with verification suite.""" |
@@ -474,10 +522,6 @@ class RealAUTest(unittest.TestCase, AUTest): |
class VirtualAUTest(unittest.TestCase, AUTest): |
"""Test harness for updating virtual machines.""" |
- # VM Constants. |
- _FULL_VDISK_SIZE = 6072 |
- _FULL_STATEFULFS_SIZE = 3074 |
- |
# Class variables used to acquire individual VM variables per test. |
_vm_lock = threading.Lock() |
_next_port = 9222 |
@@ -501,7 +545,6 @@ class VirtualAUTest(unittest.TestCase, AUTest): |
def setUp(self): |
"""Unit test overriden method. Is called before every test.""" |
AUTest.setUp(self) |
- self.vm_image_path = None |
self._AcquireUniquePortAndPidFile() |
self._KillExistingVM(self._kvm_pid_file) |
@@ -512,85 +555,37 @@ class VirtualAUTest(unittest.TestCase, AUTest): |
def ProcessOptions(cls, parser, options): |
"""Processes vm-specific options.""" |
AUTest.ProcessOptions(parser, options) |
- cls.board = options.board |
# Communicate flags to tests. |
cls.graphics_flag = '' |
if options.no_graphics: cls.graphics_flag = '--no_graphics' |
- |
- if not cls.board: |
- parser.error('Need board to convert base image to vm.') |
+ if not cls.board: parser.error('Need board to convert base image to vm.') |
def PrepareBase(self, image_path): |
"""Creates an update-able VM based on base image.""" |
- # Needed for VM delta updates. We need to use the qemu image rather |
- # than the base image on a first update. By tracking the first_update |
- # we can set src_image to the qemu form of the base image when |
- # performing generating the delta payload. |
- self._first_update = True |
- self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname( |
- image_path) |
- if not os.path.exists(self.vm_image_path): |
- Info('Creating %s' % vm_image_path) |
- RunCommand(['%s/image_to_vm.sh' % self.crosutils, |
- '--full', |
- '--from=%s' % ReinterpretPathForChroot( |
- os.path.dirname(image_path)), |
- '--vdisk_size=%s' % self._FULL_VDISK_SIZE, |
- '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, |
- '--board=%s' % self.board, |
- '--test_image'], enter_chroot=True) |
- |
- Info('Using %s as base' % self.vm_image_path) |
- self.assertTrue(os.path.exists(self.vm_image_path)) |
+ self._PrepareVMBase(image_path) |
def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
- proxy_port=''): |
+ proxy_port='', private_key_path=None): |
"""Updates VM image with image_path.""" |
stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
if src_image_path and self._first_update: |
src_image_path = self.vm_image_path |
self._first_update = False |
- # Check image payload cache first. |
- update_id = _GenerateUpdateId(target=image_path, src=src_image_path) |
- cache_path = dev_server_cache[update_id] |
- if cache_path: |
- Info('Using cache %s' % cache_path) |
- update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) |
- cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
- '--vm_image_path=%s' % self.vm_image_path, |
- '--snapshot', |
- self.graphics_flag, |
- '--persist', |
- '--kvm_pid=%s' % self._kvm_pid_file, |
- '--ssh_port=%s' % self._ssh_port, |
- stateful_change_flag, |
- '--update_url=%s' % update_url, |
- ] |
- else: |
- cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
- '--update_image_path=%s' % image_path, |
- '--vm_image_path=%s' % self.vm_image_path, |
- '--snapshot', |
- self.graphics_flag, |
- '--persist', |
- '--kvm_pid=%s' % self._kvm_pid_file, |
- '--ssh_port=%s' % self._ssh_port, |
- stateful_change_flag, |
- '--src_image=%s' % src_image_path, |
- '--proxy_port=%s' % proxy_port |
- ] |
+ cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
+ '--vm_image_path=%s' % self.vm_image_path, |
+ '--snapshot', |
+ self.graphics_flag, |
+ '--persist', |
+ '--kvm_pid=%s' % self._kvm_pid_file, |
+ '--ssh_port=%s' % self._ssh_port, |
+ stateful_change_flag, |
+ ] |
- if self.verbose: |
- try: |
- RunCommand(cmd) |
- except Exception, e: |
- raise UpdateException(1, e.message) |
- else: |
- (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
- if code != 0: |
- raise UpdateException(code, stdout) |
+ self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port, |
+ private_key_path) |
+ self.RunUpdateCmd(cmd) |
def _UpdateUsingPayload(self, update_path, stateful_change='old', |
proxy_port=None): |
@@ -606,19 +601,8 @@ class VirtualAUTest(unittest.TestCase, AUTest): |
'--ssh_port=%s' % self._ssh_port, |
stateful_change_flag, |
] |
- |
- if proxy_port: |
- cmd.append('--proxy_port=%s' % proxy_port) |
- |
- if self.verbose: |
- try: |
- RunCommand(cmd) |
- except Exception, e: |
- raise UpdateException(1, e.message) |
- else: |
- (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
- if code != 0: |
- raise UpdateException(code, stdout) |
+ if proxy_port: cmd.append('--proxy_port=%s' % proxy_port) |
+ self.RunUpdateCmd(cmd) |
def VerifyImage(self, percent_required_to_pass): |
"""Runs vm smoke suite to verify image.""" |
@@ -642,8 +626,12 @@ class VirtualAUTest(unittest.TestCase, AUTest): |
return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
-class GenerateVirtualAUDeltasTest(VirtualAUTest): |
- """Class the overrides VirtualAUTest and stores deltas we will generate.""" |
+class PregenerateAUDeltas(unittest.TestCase, AUTest): |
+ """Magical class that emulates an AUTest to store deltas we will generate. |
+ |
+ This class emulates an AUTest such that when it runs as a TestCase it runs |
+ through the exact up |
+ """ |
delta_list = {} |
def setUp(self): |
@@ -652,16 +640,29 @@ class GenerateVirtualAUDeltasTest(VirtualAUTest): |
def tearDown(self): |
pass |
+ @classmethod |
+ def ProcessOptions(cls, parser, options): |
+ AUTest.ProcessOptions(parser, options) |
+ cls.au_type = options.type |
+ |
+ def PrepareBase(self, image_path): |
+ if self.au_type == 'vm': |
+ self._PrepareVMBase(image_path) |
+ else: |
+ self._PrepareRealBase(image_path) |
+ |
def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
- proxy_port=None): |
- if src_image_path and self._first_update: |
+ proxy_port=None, private_key_path=None): |
+ if self.au_type == 'vm' and src_image_path and self._first_update: |
src_image_path = self.vm_image_path |
self._first_update = False |
+ # Generate a value that combines delta with private key path. |
+ val = '%s+%s' % (src_image_path, private_key_path) |
if not self.delta_list.has_key(image_path): |
- self.delta_list[image_path] = set([src_image_path]) |
+ self.delta_list[image_path] = set([val]) |
else: |
- self.delta_list[image_path].add(src_image_path) |
+ self.delta_list[image_path].add(val) |
def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): |
pass |
@@ -756,15 +757,16 @@ class DevServerWrapper(threading.Thread): |
return url |
-def _GenerateUpdateId(target, src): |
+def _GenerateUpdateId(target, src, key): |
"""Returns a simple representation id of target and src paths.""" |
- if src: |
- return '%s->%s' % (target, src) |
- else: |
- return target |
+ update_id = target |
+ if src: update_id = '->'.join([update_id, src]) |
+ if key: update_id = '+'.join([update_id, key]) |
+ return update_id |
-def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args, print_status): |
+def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args, |
+ print_status): |
"""Runs set number of specified jobs in parallel. |
@@ -834,40 +836,47 @@ def _PregenerateUpdates(parser, options): |
Raises: |
UpdateException if we fail to generate an update. |
""" |
- def _GenerateVMUpdate(target, src): |
+ def _GenerateVMUpdate(target, src, private_key_path): |
"""Generates an update using the devserver.""" |
- target = ReinterpretPathForChroot(target) |
- if src: |
- src = ReinterpretPathForChroot(src) |
- |
- return RunCommandCaptureOutput(['./enter_chroot.sh', |
- '--nogit_config', |
- '--', |
- 'sudo', |
- './start_devserver', |
- '--pregenerate_update', |
- '--exit', |
- '--image=%s' % target, |
- '--src_image=%s' % src, |
- '--for_vm', |
- ], combine_stdout_stderr=True, |
- print_cmd=False) |
+ command = ['./enter_chroot.sh', |
sosa
2011/02/10 06:14:35
Just a refactor to just append rather than +
|
+ '--nogit_config', |
+ '--', |
+ 'sudo', |
+ './start_devserver', |
+ '--pregenerate_update', |
+ '--exit', |
+ ] |
+ # Add actual args to command. |
+ command.append('--image=%s' % ReinterpretPathForChroot(target)) |
+ if src: command.append('--src_image=%s' % ReinterpretPathForChroot(src)) |
+ if options.type == 'vm': command.append('--for_vm') |
+ if private_key_path: |
+ command.append('--private_key=%s' % |
+ ReinterpretPathForChroot(private_key_path)) |
+ |
+ return RunCommandCaptureOutput(command, combine_stdout_stderr=True, |
+ print_cmd=True) |
# Get the list of deltas by mocking out update method in test class. |
- test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) |
+ test_suite = _PrepareTestSuite(parser, options, PregenerateAUDeltas) |
test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
+ if not test_result.wasSuccessful(): |
sosa
2011/02/10 06:14:35
This was a bug being missing.
|
+ raise UpdateException(1, 'Error finding updates to generate.') |
Info('The following delta updates are required.') |
update_ids = [] |
jobs = [] |
args = [] |
- for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): |
- for src in srcs: |
- update_id = _GenerateUpdateId(target=target, src=src) |
+ for target, srcs in PregenerateAUDeltas.delta_list.items(): |
+ for src_key in srcs: |
+ (src, key) = src_key.split('+') |
+ # TODO(sosa): Add private key as part of caching name once devserver can |
+ # handle it its own cache. |
+ update_id = _GenerateUpdateId(target=target, src=src, key=key) |
print >> sys.stderr, 'AU: %s' % update_id |
update_ids.append(update_id) |
jobs.append(_GenerateVMUpdate) |
- args.append((target, src)) |
+ args.append((target, src, key)) |
raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True) |
results = [] |
@@ -919,6 +928,42 @@ def _RunTestsInParallel(parser, options, test_class): |
Die('Test harness was not successful') |
+def _InsertPrivateKeyIntoImage(image_path, private_key_path): |
sosa
2011/02/10 06:14:35
Core component that adds the public key in the rig
|
+ from_dir = os.path.dirname(image_path) |
+ image = os.path.basename(image_path) |
+ crosutils_dir = os.path.abspath(__file__).rsplit('/', 2)[0] |
+ target_key_path = 'usr/share/update_engine/update-payload-key.pub.pem' |
+ |
+ # Temporary directories for this function. |
+ rootfs_dir = tempfile.mkdtemp(suffix='rootfs', prefix='tmp') |
+ stateful_dir = tempfile.mkdtemp(suffix='stateful', prefix='tmp') |
+ |
+ Info('Copying %s into %s' % (private_key_path, image_path)) |
+ try: |
+ RunCommand(['%s/mount_gpt_image.sh' % crosutils_dir, |
+ '--from=%s' % from_dir, |
+ '--image=%s' % image, |
+ '--rootfs_mountpt=%s' % rootfs_dir, |
+ '--stateful_mountpt=%s' % stateful_dir, |
+ ], print_cmd=False, redirect_stdout=True, |
+ redirect_stderr=True) |
+ path = os.path.join(rootfs_dir, target_key_path) |
+ dir_path = os.path.dirname(path) |
+ RunCommand(['sudo', 'mkdir', '--parents', dir_path], print_cmd=False) |
+ RunCommand(['sudo', 'cp', '--force', '-p', private_key_path, path], |
+ print_cmd=False) |
+ finally: |
+ # Unmount best effort regardless. |
+ RunCommand(['%s/mount_gpt_image.sh' % crosutils_dir, |
+ '--unmount', |
+ '--rootfs_mountpt=%s' % rootfs_dir, |
+ '--stateful_mountpt=%s' % stateful_dir, |
+ ], print_cmd=False, redirect_stdout=True, redirect_stderr=True) |
+ # Clean up our directories. |
+ os.rmdir(rootfs_dir) |
+ os.rmdir(stateful_dir) |
+ |
+ |
def main(): |
parser = optparse.OptionParser() |
parser.add_option('-b', '--base_image', |
@@ -932,6 +977,10 @@ def main(): |
help='Disable graphics for the vm test.') |
parser.add_option('-j', '--jobs', default=8, type=int, |
help='Number of simultaneous jobs') |
+ parser.add_option('--public_key', default=None, |
+ help='Public key to use on images and updates.') |
+ parser.add_option('--private_key', default=None, |
+ help='Private key to use on images and updates.') |
parser.add_option('-q', '--quick_test', default=False, action='store_true', |
help='Use a basic test to verify image.') |
parser.add_option('-m', '--remote', |
@@ -951,11 +1000,23 @@ def main(): |
if leftover_args: |
parser.error('Found extra options we do not support: %s' % leftover_args) |
+ assert options.target_image and os.path.exists(options.target_image), \ |
+ 'Target image path does not exist' |
+ if not options.base_image: |
+ Warning('Base image not specified. Using target image as base image.') |
+ options.base_image = options.target_image |
+ |
# Figure out the test_class. |
if options.type == 'vm': test_class = VirtualAUTest |
elif options.type == 'real': test_class = RealAUTest |
else: parser.error('Could not parse harness type %s.' % options.type) |
+ if options.private_key or options.public_key: |
+ assert os.path.exists(options.private_key), 'Could not find private key.' |
+ assert os.path.exists(options.public_key), 'Could not find public key.' |
+ _InsertPrivateKeyIntoImage(options.target_image, options.public_key) |
+ _InsertPrivateKeyIntoImage(options.base_image, options.public_key) |
+ |
# TODO(sosa): Caching doesn't really make sense on non-vm images (yet). |
global dev_server_cache |
if options.type == 'vm' and options.jobs > 1: |