Update test harness to take in optional public and private keys to sign payloads.

In addition some refactoring to make this cleaner and easier.

Change-Id: I1607700d065c71aff2b2833b10acbd3ebace68ce

BUG=chromium-os:8212
TEST=Ran with Simple ... running now with full

Review URL: http://codereview.chromium.org/6482017
This commit is contained in:
Chris Sosa 2011-02-14 15:29:32 -08:00
parent 1ed7fe6b70
commit 8dad50d97f
4 changed files with 282 additions and 196 deletions

View File

@ -15,8 +15,10 @@
import optparse import optparse
import os import os
import re import re
import shutil
import subprocess import subprocess
import sys import sys
import tempfile
import threading import threading
import time import time
import unittest import unittest
@ -53,11 +55,74 @@ class AUTest(object):
self.crosutils = os.path.join(os.path.dirname(__file__), '..') self.crosutils = os.path.join(os.path.dirname(__file__), '..')
self.crosutilsbin = 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.download_folder = os.path.join(self.crosutils, 'latest_download')
self.vm_image_path = None
if not os.path.exists(self.download_folder): if not os.path.exists(self.download_folder):
os.makedirs(self.download_folder) os.makedirs(self.download_folder)
# -------- Helper functions --------- # -------- Helper functions ---------
def _PrepareRealBase(self, image_path):
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): def GetStatefulChangeFlag(self, stateful_change):
"""Returns the flag to pass to image_to_vm for the stateful change.""" """Returns the flag to pass to image_to_vm for the stateful change."""
stateful_change_flag = '' stateful_change_flag = ''
@ -101,7 +166,7 @@ class AUTest(object):
return percent_passed return percent_passed
def PerformUpdate(self, image_path, src_image_path='', stateful_change='old', 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. """Performs an update using _UpdateImage and reports any error.
Subclasses should not override this method but override _UpdateImage Subclasses should not override this method but override _UpdateImage
@ -121,10 +186,14 @@ class AUTest(object):
Raises an UpdateException if _UpdateImage returns an error. Raises an UpdateException if _UpdateImage returns an error.
""" """
try: try:
if not self.use_delta_updates: if not self.use_delta_updates: src_image_path = ''
src_image_path = '' if private_key_path:
key_to_use = private_key_path
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: except UpdateException as err:
# If the update fails, print it out # If the update fails, print it out
Warning(err.stdout) Warning(err.stdout)
@ -178,6 +247,9 @@ class AUTest(object):
cls.base_image_path = options.base_image cls.base_image_path = options.base_image
cls.target_image_path = options.target_image cls.target_image_path = options.target_image
cls.use_delta_updates = options.delta cls.use_delta_updates = options.delta
cls.board = options.board
cls.private_key = options.private_key
cls.clean = options.clean
if options.quick_test: if options.quick_test:
cls.verify_suite = 'build_RootFilesystemSize' cls.verify_suite = 'build_RootFilesystemSize'
else: else:
@ -199,7 +271,7 @@ class AUTest(object):
pass pass
def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 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. """Implementation of an actual update.
See PerformUpdate for description of args. Subclasses must override this See PerformUpdate for description of args. Subclasses must override this
@ -410,32 +482,20 @@ class RealAUTest(unittest.TestCase, AUTest):
def PrepareBase(self, image_path): def PrepareBase(self, image_path):
"""Auto-update to base image to prepare for test.""" """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', 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.""" """Updates a remote image using image_to_live.sh."""
stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
cmd = ['%s/image_to_live.sh' % self.crosutils, cmd = ['%s/image_to_live.sh' % self.crosutils,
'--image=%s' % image_path,
'--remote=%s' % self.remote, '--remote=%s' % self.remote,
stateful_change_flag, stateful_change_flag,
'--verify', '--verify',
'--src_image=%s' % src_image_path
] ]
self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port,
if proxy_port: private_key_path)
cmd.append('--proxy_port=%s' % proxy_port) self.RunUpdateCmd(cmd)
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 _UpdateUsingPayload(self, update_path, stateful_change='old', def _UpdateUsingPayload(self, update_path, stateful_change='old',
proxy_port=None): proxy_port=None):
@ -447,19 +507,8 @@ class RealAUTest(unittest.TestCase, AUTest):
stateful_change_flag, stateful_change_flag,
'--verify', '--verify',
] ]
if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
if proxy_port: self.RunUpdateCmd(cmd)
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)
def VerifyImage(self, percent_required_to_pass): def VerifyImage(self, percent_required_to_pass):
"""Verifies an image using run_remote_tests.sh with verification suite.""" """Verifies an image using run_remote_tests.sh with verification suite."""
@ -474,10 +523,6 @@ class RealAUTest(unittest.TestCase, AUTest):
class VirtualAUTest(unittest.TestCase, AUTest): class VirtualAUTest(unittest.TestCase, AUTest):
"""Test harness for updating virtual machines.""" """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. # Class variables used to acquire individual VM variables per test.
_vm_lock = threading.Lock() _vm_lock = threading.Lock()
_next_port = 9222 _next_port = 9222
@ -501,7 +546,6 @@ class VirtualAUTest(unittest.TestCase, AUTest):
def setUp(self): def setUp(self):
"""Unit test overriden method. Is called before every test.""" """Unit test overriden method. Is called before every test."""
AUTest.setUp(self) AUTest.setUp(self)
self.vm_image_path = None
self._AcquireUniquePortAndPidFile() self._AcquireUniquePortAndPidFile()
self._KillExistingVM(self._kvm_pid_file) self._KillExistingVM(self._kvm_pid_file)
@ -512,52 +556,24 @@ class VirtualAUTest(unittest.TestCase, AUTest):
def ProcessOptions(cls, parser, options): def ProcessOptions(cls, parser, options):
"""Processes vm-specific options.""" """Processes vm-specific options."""
AUTest.ProcessOptions(parser, options) AUTest.ProcessOptions(parser, options)
cls.board = options.board
# Communicate flags to tests. # Communicate flags to tests.
cls.graphics_flag = '' cls.graphics_flag = ''
if options.no_graphics: cls.graphics_flag = '--no_graphics' 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): def PrepareBase(self, image_path):
"""Creates an update-able VM based on base image.""" """Creates an update-able VM based on base image."""
# Needed for VM delta updates. We need to use the qemu image rather self._PrepareVMBase(image_path)
# 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))
def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 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.""" """Updates VM image with image_path."""
stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
if src_image_path and self._first_update: if src_image_path and self._first_update:
src_image_path = self.vm_image_path src_image_path = self.vm_image_path
self._first_update = False 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, cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
'--vm_image_path=%s' % self.vm_image_path, '--vm_image_path=%s' % self.vm_image_path,
'--snapshot', '--snapshot',
@ -566,31 +582,11 @@ class VirtualAUTest(unittest.TestCase, AUTest):
'--kvm_pid=%s' % self._kvm_pid_file, '--kvm_pid=%s' % self._kvm_pid_file,
'--ssh_port=%s' % self._ssh_port, '--ssh_port=%s' % self._ssh_port,
stateful_change_flag, 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
] ]
if self.verbose: self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port,
try: private_key_path)
RunCommand(cmd) self.RunUpdateCmd(cmd)
except Exception, e:
raise UpdateException(1, e.message)
else:
(code, stdout, stderr) = RunCommandCaptureOutput(cmd)
if code != 0:
raise UpdateException(code, stdout)
def _UpdateUsingPayload(self, update_path, stateful_change='old', def _UpdateUsingPayload(self, update_path, stateful_change='old',
proxy_port=None): proxy_port=None):
@ -606,19 +602,8 @@ class VirtualAUTest(unittest.TestCase, AUTest):
'--ssh_port=%s' % self._ssh_port, '--ssh_port=%s' % self._ssh_port,
stateful_change_flag, stateful_change_flag,
] ]
if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
if proxy_port: self.RunUpdateCmd(cmd)
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)
def VerifyImage(self, percent_required_to_pass): def VerifyImage(self, percent_required_to_pass):
"""Runs vm smoke suite to verify image.""" """Runs vm smoke suite to verify image."""
@ -642,8 +627,12 @@ class VirtualAUTest(unittest.TestCase, AUTest):
return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
class GenerateVirtualAUDeltasTest(VirtualAUTest): class PregenerateAUDeltas(unittest.TestCase, AUTest):
"""Class the overrides VirtualAUTest and stores deltas we will generate.""" """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 = {} delta_list = {}
def setUp(self): def setUp(self):
@ -652,16 +641,29 @@ class GenerateVirtualAUDeltasTest(VirtualAUTest):
def tearDown(self): def tearDown(self):
pass 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', def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
proxy_port=None): proxy_port=None, private_key_path=None):
if src_image_path and self._first_update: if self.au_type == 'vm' and src_image_path and self._first_update:
src_image_path = self.vm_image_path src_image_path = self.vm_image_path
self._first_update = False 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): 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: else:
self.delta_list[image_path].add(src_image_path) self.delta_list[image_path].add(val)
def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg):
pass pass
@ -756,15 +758,16 @@ class DevServerWrapper(threading.Thread):
return url return url
def _GenerateUpdateId(target, src): def _GenerateUpdateId(target, src, key):
"""Returns a simple representation id of target and src paths.""" """Returns a simple representation id of target and src paths."""
if src: update_id = target
return '%s->%s' % (target, src) if src: update_id = '->'.join([update_id, src])
else: if key: update_id = '+'.join([update_id, key])
return target 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. """Runs set number of specified jobs in parallel.
@ -834,40 +837,47 @@ def _PregenerateUpdates(parser, options):
Raises: Raises:
UpdateException if we fail to generate an update. 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.""" """Generates an update using the devserver."""
target = ReinterpretPathForChroot(target) command = ['./enter_chroot.sh',
if src:
src = ReinterpretPathForChroot(src)
return RunCommandCaptureOutput(['./enter_chroot.sh',
'--nogit_config', '--nogit_config',
'--', '--',
'sudo', 'sudo',
'./start_devserver', './start_devserver',
'--pregenerate_update', '--pregenerate_update',
'--exit', '--exit',
'--image=%s' % target, ]
'--src_image=%s' % src, # Add actual args to command.
'--for_vm', command.append('--image=%s' % ReinterpretPathForChroot(target))
], combine_stdout_stderr=True, if src: command.append('--src_image=%s' % ReinterpretPathForChroot(src))
print_cmd=False) 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. # 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) test_result = unittest.TextTestRunner(verbosity=0).run(test_suite)
if not test_result.wasSuccessful():
raise UpdateException(1, 'Error finding updates to generate.')
Info('The following delta updates are required.') Info('The following delta updates are required.')
update_ids = [] update_ids = []
jobs = [] jobs = []
args = [] args = []
for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): for target, srcs in PregenerateAUDeltas.delta_list.items():
for src in srcs: for src_key in srcs:
update_id = _GenerateUpdateId(target=target, src=src) (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 print >> sys.stderr, 'AU: %s' % update_id
update_ids.append(update_id) update_ids.append(update_id)
jobs.append(_GenerateVMUpdate) jobs.append(_GenerateVMUpdate)
args.append((target, src)) args.append((target, src, key))
raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True) raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True)
results = [] results = []
@ -919,12 +929,74 @@ def _RunTestsInParallel(parser, options, test_class):
Die('Test harness was not successful') Die('Test harness was not successful')
def InsertPublicKeyIntoImage(image_path, key_path):
"""Inserts public key into image @ static update_engine location."""
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' % (key_path, image_path))
try:
RunCommand(['./mount_gpt_image.sh',
'--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, cwd=crosutils_dir)
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', key_path, path],
print_cmd=False)
finally:
# Unmount best effort regardless.
RunCommand(['./mount_gpt_image.sh',
'--unmount',
'--rootfs_mountpt=%s' % rootfs_dir,
'--stateful_mountpt=%s' % stateful_dir,
], print_cmd=False, redirect_stdout=True, redirect_stderr=True,
cwd=crosutils_dir)
# Clean up our directories.
os.rmdir(rootfs_dir)
os.rmdir(stateful_dir)
RunCommand(['bin/cros_make_image_bootable', from_dir, image, ],
print_cmd=False, redirect_stdout=True, redirect_stderr=True,
enter_chroot=True, cwd=crosutils_dir)
def CleanPreviousWork(options):
"""Cleans up previous work from the devserver cache and local image cache."""
Info('Cleaning up previous work.')
# Wipe devserver cache.
RunCommandCaptureOutput(
['sudo', './start_devserver', '--clear_cache', '--exit', ],
enter_chroot=True, print_cmd=False, combine_stdout_stderr=True)
# Clean previous vm images if they exist.
if options.type == 'vm':
target_vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
options.target_image)
base_vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
options.base_image)
if os.path.exists(target_vm_image_path): os.remove(target_vm_image_path)
if os.path.exists(base_vm_image_path): os.remove(base_vm_image_path)
def main(): def main():
parser = optparse.OptionParser() parser = optparse.OptionParser()
parser.add_option('-b', '--base_image', parser.add_option('-b', '--base_image',
help='path to the base image.') help='path to the base image.')
parser.add_option('-r', '--board', parser.add_option('-r', '--board',
help='board for the images.') help='board for the images.')
parser.add_option('--clean', default=False, dest='clean', action='store_true',
help='Clean all previous state')
parser.add_option('--no_delta', action='store_false', default=True, parser.add_option('--no_delta', action='store_false', default=True,
dest='delta', dest='delta',
help='Disable using delta updates.') help='Disable using delta updates.')
@ -932,6 +1004,10 @@ def main():
help='Disable graphics for the vm test.') help='Disable graphics for the vm test.')
parser.add_option('-j', '--jobs', default=8, type=int, parser.add_option('-j', '--jobs', default=8, type=int,
help='Number of simultaneous jobs') 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', parser.add_option('-q', '--quick_test', default=False, action='store_true',
help='Use a basic test to verify image.') help='Use a basic test to verify image.')
parser.add_option('-m', '--remote', parser.add_option('-m', '--remote',
@ -948,31 +1024,52 @@ def main():
'possible.') 'possible.')
(options, leftover_args) = parser.parse_args() (options, leftover_args) = parser.parse_args()
if leftover_args: if leftover_args: parser.error('Found unsupported flags: %s' % 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:
Info('Base image not specified. Using target image as base image.')
options.base_image = options.target_image
# Sanity checks on keys and insert them onto the image. The caches must be
# cleaned so we know that the vm images and payloads match the possibly new
# key.
if options.private_key or options.public_key:
error_msg = ('Could not find %s key. Both private and public keys must be '
'specified if either is specified.')
assert options.private_key and os.path.exists(options.private_key), \
error_msg % 'private'
assert options.public_key and os.path.exists(options.public_key), \
error_msg % 'public'
InsertPublicKeyIntoImage(options.target_image, options.public_key)
InsertPublicKeyIntoImage(options.base_image, options.public_key)
options.clean = True
# Clean up previous work if requested.
if options.clean: CleanPreviousWork(options)
# Figure out the test_class. # Figure out the test_class.
if options.type == 'vm': test_class = VirtualAUTest if options.type == 'vm': test_class = VirtualAUTest
elif options.type == 'real': test_class = RealAUTest elif options.type == 'real': test_class = RealAUTest
else: parser.error('Could not parse harness type %s.' % options.type) else: parser.error('Could not parse harness type %s.' % options.type)
# TODO(sosa): Caching doesn't really make sense on non-vm images (yet). # Generate cache of updates to use during test harness.
global dev_server_cache global dev_server_cache
if options.type == 'vm' and options.jobs > 1:
dev_server_cache = _PregenerateUpdates(parser, options) dev_server_cache = _PregenerateUpdates(parser, options)
my_server = DevServerWrapper() my_server = DevServerWrapper()
my_server.start() my_server.start()
try: try:
if options.type == 'vm':
_RunTestsInParallel(parser, options, test_class) _RunTestsInParallel(parser, options, test_class)
finally:
my_server.Stop()
else: else:
dev_server_cache = None # TODO(sosa) - Take in a machine pool for a real test.
# Can't run in parallel with only one remote device.
test_suite = _PrepareTestSuite(parser, options, test_class) test_suite = _PrepareTestSuite(parser, options, test_class)
test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) test_result = unittest.TextTestRunner(verbosity=2).run(test_suite)
if not test_result.wasSuccessful(): if not test_result.wasSuccessful(): Die('Test harness failed.')
Die('Test harness was not successful.') finally:
my_server.Stop()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -35,7 +35,7 @@ DEFINE_string proxy_port "" \
DEFINE_string src_image "" \ DEFINE_string src_image "" \
"Create a delta update by passing in the image on the remote machine." "Create a delta update by passing in the image on the remote machine."
DEFINE_string stateful_update_flag "" "Flags to pass to stateful update." s DEFINE_string stateful_update_flag "" "Flags to pass to stateful update." s
DEFINE_string update_image_path "" "Path of the image to update to." u DEFINE_string image "" "Path of the image to update to." u
DEFINE_string update_url "" "Full url of an update image." DEFINE_string update_url "" "Full url of an update image."
DEFINE_string vm_image_path "" "Path of the VM image to update from." v DEFINE_string vm_image_path "" "Path of the VM image to update from." v
@ -52,8 +52,8 @@ trap stop_kvm EXIT
start_kvm "${FLAGS_vm_image_path}" start_kvm "${FLAGS_vm_image_path}"
retry_until_ssh retry_until_ssh
if [ -n "${FLAGS_update_image_path}" ]; then if [ -n "${FLAGS_image}" ]; then
IMAGE_ARGS="--image=$(readlink -f ${FLAGS_update_image_path})" IMAGE_ARGS="--image=$(readlink -f ${FLAGS_image})"
fi fi
if [ -n "${FLAGS_payload}" ]; then if [ -n "${FLAGS_payload}" ]; then

View File

@ -225,17 +225,8 @@ def GrabZipAndExtractImage(zip_url, download_folder, image_name) :
fh.close() fh.close()
def WipeDevServerCache():
"""Wipes the cache of the dev server."""
RunCommand(['sudo',
'./start_devserver',
'--clear_cache',
'--exit',
], enter_chroot=True)
def RunAUTestHarness(board, channel, latest_url_base, zip_server_base, def RunAUTestHarness(board, channel, latest_url_base, zip_server_base,
no_graphics, type, remote): no_graphics, type, remote, clean):
"""Runs the auto update test harness. """Runs the auto update test harness.
The auto update test harness encapsulates testing the auto-update mechanism The auto update test harness encapsulates testing the auto-update mechanism
@ -251,6 +242,7 @@ def RunAUTestHarness(board, channel, latest_url_base, zip_server_base,
no_graphics: boolean - If True, disable graphics during vm test. no_graphics: boolean - If True, disable graphics during vm test.
type: which test harness to run. Possible values: real, vm. type: which test harness to run. Possible values: real, vm.
remote: ip address for real test harness run. remote: ip address for real test harness run.
clean: Clean the state of test harness before running.
""" """
crosutils_root = os.path.join(os.path.dirname(__file__), '..') crosutils_root = os.path.join(os.path.dirname(__file__), '..')
download_folder = os.path.abspath('latest_download') download_folder = os.path.abspath('latest_download')
@ -262,6 +254,9 @@ def RunAUTestHarness(board, channel, latest_url_base, zip_server_base,
cwd=crosutils_root, redirect_stdout=True, cwd=crosutils_root, redirect_stdout=True,
print_cmd=True).strip() print_cmd=True).strip()
update_engine_path = os.path.join(crosutils_root, '..', 'platform',
'update_engine')
cmd = ['bin/cros_au_test_harness', cmd = ['bin/cros_au_test_harness',
'--base_image=%s' % os.path.join(download_folder, '--base_image=%s' % os.path.join(download_folder,
_IMAGE_TO_EXTRACT), _IMAGE_TO_EXTRACT),
@ -270,8 +265,13 @@ def RunAUTestHarness(board, channel, latest_url_base, zip_server_base,
'--board=%s' % board, '--board=%s' % board,
'--type=%s' % type, '--type=%s' % type,
'--remote=%s' % remote, '--remote=%s' % remote,
'--private_key=%s' % os.path.join(update_engine_path,
'unittest_key.pem'),
'--public_key=%s' % os.path.join(update_engine_path,
'unittest_key.pub.pem'),
] ]
if no_graphics: cmd.append('--no_graphics') if no_graphics: cmd.append('--no_graphics')
if clean: cmd.append('--clean')
RunCommand(cmd, cwd=crosutils_root) RunCommand(cmd, cwd=crosutils_root)
@ -299,25 +299,14 @@ def main():
parser.set_usage(parser.format_help()) parser.set_usage(parser.format_help())
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if args: if args: parser.error('Extra args found %s.' % args)
parser.error('Extra args found %s.' % args) if not options.board: parser.error('Need board for image to compare against.')
if not options.channel: parser.error('Need channel e.g. dev-channel.')
if not options.board: if not options.zipbase: parser.error('Need zip url base to get images.')
parser.error('Need board for image to compare against.')
if not options.channel:
parser.error('Need channel for image to compare against.')
if not options.zipbase:
parser.error('Need zip url base to get images.')
if not options.cache:
Info('Wiping dev server cache.')
WipeDevServerCache()
RunAUTestHarness(options.board, options.channel, options.latestbase, RunAUTestHarness(options.board, options.channel, options.latestbase,
options.zipbase, options.no_graphics, options.type, options.zipbase, options.no_graphics, options.type,
options.remote) options.remote, not options.cache)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -50,7 +50,7 @@ DEFINE_boolean factory_install $FLAGS_FALSE \
# We default to TRUE so the buildbot gets its image. Note this is different # We default to TRUE so the buildbot gets its image. Note this is different
# behavior from image_to_usb.sh # behavior from image_to_usb.sh
DEFINE_boolean force_copy ${FLAGS_TRUE} "Always rebuild test image" DEFINE_boolean force_copy ${FLAGS_FALSE} "Always rebuild test image"
DEFINE_string format "qemu" \ DEFINE_string format "qemu" \
"Output format, either qemu, vmware or virtualbox" "Output format, either qemu, vmware or virtualbox"
DEFINE_string from "" \ DEFINE_string from "" \