Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(154)

Side by Side Diff: bin/cros_au_test_harness.py

Issue 6482017: Update test harness to take in optional public and private keys to sign payloads. (Closed) Base URL: http://git.chromium.org/git/crosutils.git@master
Patch Set: Fix a bug I found Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | bin/cros_run_vm_update » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """This module runs a suite of Auto Update tests. 7 """This module runs a suite of Auto Update tests.
8 8
9 The tests can be run on either a virtual machine or actual device depending 9 The tests can be run on either a virtual machine or actual device depending
10 on parameters given. Specific tests can be run by invoking --test_prefix. 10 on parameters given. Specific tests can be run by invoking --test_prefix.
11 Verbose is useful for many of the tests if you want to see individual commands 11 Verbose is useful for many of the tests if you want to see individual commands
12 being run during the update process. 12 being run during the update process.
13 """ 13 """
14 14
15 import optparse 15 import optparse
16 import os 16 import os
17 import re 17 import re
18 import shutil
18 import subprocess 19 import subprocess
19 import sys 20 import sys
21 import tempfile
20 import threading 22 import threading
21 import time 23 import time
22 import unittest 24 import unittest
23 import urllib 25 import urllib
24 26
25 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) 27 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib'))
26 from cros_build_lib import Die 28 from cros_build_lib import Die
27 from cros_build_lib import GetIPAddress 29 from cros_build_lib import GetIPAddress
28 from cros_build_lib import Info 30 from cros_build_lib import Info
29 from cros_build_lib import ReinterpretPathForChroot 31 from cros_build_lib import ReinterpretPathForChroot
(...skipping 16 matching lines...) Expand all
46 class AUTest(object): 48 class AUTest(object):
47 """Abstract interface that defines an Auto Update test.""" 49 """Abstract interface that defines an Auto Update test."""
48 verbose = False 50 verbose = False
49 51
50 def setUp(self): 52 def setUp(self):
51 unittest.TestCase.setUp(self) 53 unittest.TestCase.setUp(self)
52 # Set these up as they are used often. 54 # Set these up as they are used often.
53 self.crosutils = os.path.join(os.path.dirname(__file__), '..') 55 self.crosutils = os.path.join(os.path.dirname(__file__), '..')
54 self.crosutilsbin = os.path.join(os.path.dirname(__file__)) 56 self.crosutilsbin = os.path.join(os.path.dirname(__file__))
55 self.download_folder = os.path.join(self.crosutils, 'latest_download') 57 self.download_folder = os.path.join(self.crosutils, 'latest_download')
58 self.vm_image_path = None
56 if not os.path.exists(self.download_folder): 59 if not os.path.exists(self.download_folder):
57 os.makedirs(self.download_folder) 60 os.makedirs(self.download_folder)
58 61
59 # -------- Helper functions --------- 62 # -------- Helper functions ---------
60 63
64 def _PrepareRealBase(self, image_path):
65 self.PerformUpdate(image_path)
66
67 def _PrepareVMBase(self, image_path):
68 # VM Constants.
69 FULL_VDISK_SIZE = 6072
70 FULL_STATEFULFS_SIZE = 3074
71 # Needed for VM delta updates. We need to use the qemu image rather
72 # than the base image on a first update. By tracking the first_update
73 # we can set src_image to the qemu form of the base image when
74 # performing generating the delta payload.
75 self._first_update = True
76 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
77 image_path)
78 if not os.path.exists(self.vm_image_path):
79 Info('Creating %s' % self.vm_image_path)
80 RunCommand(['%s/image_to_vm.sh' % self.crosutils,
81 '--full',
82 '--from=%s' % ReinterpretPathForChroot(
83 os.path.dirname(image_path)),
84 '--vdisk_size=%s' % FULL_VDISK_SIZE,
85 '--statefulfs_size=%s' % FULL_STATEFULFS_SIZE,
86 '--board=%s' % self.board,
87 '--test_image'], enter_chroot=True)
88
89 Info('Using %s as base' % self.vm_image_path)
90 self.assertTrue(os.path.exists(self.vm_image_path))
91
92 def AppendUpdateFlags(self, cmd, image_path, src_image_path, proxy_port,
93 private_key_path):
94 """Appends common args to an update cmd defined by an array.
95
96 Modifies cmd in places by appending appropriate items given args.
97 """
98 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
99
100 # Get pregenerated update if we have one.
101 update_id = _GenerateUpdateId(target=image_path, src=src_image_path,
102 key=private_key_path)
103 cache_path = dev_server_cache[update_id]
104 if cache_path:
105 update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path)
106 cmd.append('--update_url=%s' % update_url)
107 else:
108 cmd.append('--image=%s' % image_path)
109 if src_image_path: cmd.append('--src_image=%s' % src_image_path)
110
111 def RunUpdateCmd(self, cmd):
112 """Runs the given update cmd given verbose options.
113
114 Raises an UpdateException if the update fails.
115 """
116 if self.verbose:
117 try:
118 RunCommand(cmd)
119 except Exception, e:
120 raise UpdateException(1, e.message)
121 else:
122 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
123 if code != 0:
124 raise UpdateException(code, stdout)
125
61 def GetStatefulChangeFlag(self, stateful_change): 126 def GetStatefulChangeFlag(self, stateful_change):
62 """Returns the flag to pass to image_to_vm for the stateful change.""" 127 """Returns the flag to pass to image_to_vm for the stateful change."""
63 stateful_change_flag = '' 128 stateful_change_flag = ''
64 if stateful_change: 129 if stateful_change:
65 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change 130 stateful_change_flag = '--stateful_update_flag=%s' % stateful_change
66 131
67 return stateful_change_flag 132 return stateful_change_flag
68 133
69 def _ParseGenerateTestReportOutput(self, output): 134 def _ParseGenerateTestReportOutput(self, output):
70 """Returns the percentage of tests that passed based on output.""" 135 """Returns the percentage of tests that passed based on output."""
(...skipping 23 matching lines...) Expand all
94 Info('Output from VerifyImage():') 159 Info('Output from VerifyImage():')
95 print >> sys.stderr, output 160 print >> sys.stderr, output
96 sys.stderr.flush() 161 sys.stderr.flush()
97 percent_passed = self._ParseGenerateTestReportOutput(output) 162 percent_passed = self._ParseGenerateTestReportOutput(output)
98 Info('Percent passed: %d vs. Percent required: %d' % ( 163 Info('Percent passed: %d vs. Percent required: %d' % (
99 percent_passed, percent_required_to_pass)) 164 percent_passed, percent_required_to_pass))
100 unittest.assertTrue(percent_passed >= percent_required_to_pass) 165 unittest.assertTrue(percent_passed >= percent_required_to_pass)
101 return percent_passed 166 return percent_passed
102 167
103 def PerformUpdate(self, image_path, src_image_path='', stateful_change='old', 168 def PerformUpdate(self, image_path, src_image_path='', stateful_change='old',
104 proxy_port=None): 169 proxy_port=None, private_key_path=None):
105 """Performs an update using _UpdateImage and reports any error. 170 """Performs an update using _UpdateImage and reports any error.
106 171
107 Subclasses should not override this method but override _UpdateImage 172 Subclasses should not override this method but override _UpdateImage
108 instead. 173 instead.
109 174
110 Args: 175 Args:
111 image_path: Path to the image to update with. This image must be a test 176 image_path: Path to the image to update with. This image must be a test
112 image. 177 image.
113 src_image_path: Optional. If set, perform a delta update using the 178 src_image_path: Optional. If set, perform a delta update using the
114 image specified by the path as the source image. 179 image specified by the path as the source image.
115 stateful_change: How to modify the stateful partition. Values are: 180 stateful_change: How to modify the stateful partition. Values are:
116 'old': Don't modify stateful partition. Just update normally. 181 'old': Don't modify stateful partition. Just update normally.
117 'clean': Uses clobber-state to wipe the stateful partition with the 182 'clean': Uses clobber-state to wipe the stateful partition with the
118 exception of code needed for ssh. 183 exception of code needed for ssh.
119 proxy_port: Port to have the client connect to. For use with 184 proxy_port: Port to have the client connect to. For use with
120 CrosTestProxy. 185 CrosTestProxy.
121 Raises an UpdateException if _UpdateImage returns an error. 186 Raises an UpdateException if _UpdateImage returns an error.
122 """ 187 """
123 try: 188 try:
124 if not self.use_delta_updates: 189 if not self.use_delta_updates: src_image_path = ''
125 src_image_path = '' 190 if private_key_path:
191 key_to_use = private_key_path
192 else:
193 key_to_use = self.private_key
126 194
127 self._UpdateImage(image_path, src_image_path, stateful_change, proxy_port) 195 self._UpdateImage(image_path, src_image_path, stateful_change, proxy_port,
196 key_to_use)
128 except UpdateException as err: 197 except UpdateException as err:
129 # If the update fails, print it out 198 # If the update fails, print it out
130 Warning(err.stdout) 199 Warning(err.stdout)
131 raise 200 raise
132 201
133 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): 202 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg):
134 """Attempt a payload update, expect it to fail with expected log""" 203 """Attempt a payload update, expect it to fail with expected log"""
135 try: 204 try:
136 self._UpdateUsingPayload(payload) 205 self._UpdateUsingPayload(payload)
137 except UpdateException as err: 206 except UpdateException as err:
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 def ProcessOptions(cls, parser, options): 240 def ProcessOptions(cls, parser, options):
172 """Processes options. 241 """Processes options.
173 242
174 Static method that should be called from main. Subclasses should also 243 Static method that should be called from main. Subclasses should also
175 call their parent method if they override it. 244 call their parent method if they override it.
176 """ 245 """
177 cls.verbose = options.verbose 246 cls.verbose = options.verbose
178 cls.base_image_path = options.base_image 247 cls.base_image_path = options.base_image
179 cls.target_image_path = options.target_image 248 cls.target_image_path = options.target_image
180 cls.use_delta_updates = options.delta 249 cls.use_delta_updates = options.delta
250 cls.board = options.board
251 cls.private_key = options.private_key
252 cls.clean = options.clean
181 if options.quick_test: 253 if options.quick_test:
182 cls.verify_suite = 'build_RootFilesystemSize' 254 cls.verify_suite = 'build_RootFilesystemSize'
183 else: 255 else:
184 cls.verify_suite = 'suite_Smoke' 256 cls.verify_suite = 'suite_Smoke'
185 257
186 # Sanity checks. 258 # Sanity checks.
187 if not cls.base_image_path: 259 if not cls.base_image_path:
188 parser.error('Need path to base image for vm.') 260 parser.error('Need path to base image for vm.')
189 elif not os.path.exists(cls.base_image_path): 261 elif not os.path.exists(cls.base_image_path):
190 Die('%s does not exist' % cls.base_image_path) 262 Die('%s does not exist' % cls.base_image_path)
191 263
192 if not cls.target_image_path: 264 if not cls.target_image_path:
193 parser.error('Need path to target image to update with.') 265 parser.error('Need path to target image to update with.')
194 elif not os.path.exists(cls.target_image_path): 266 elif not os.path.exists(cls.target_image_path):
195 Die('%s does not exist' % cls.target_image_path) 267 Die('%s does not exist' % cls.target_image_path)
196 268
197 def PrepareBase(self, image_path): 269 def PrepareBase(self, image_path):
198 """Prepares target with base_image_path.""" 270 """Prepares target with base_image_path."""
199 pass 271 pass
200 272
201 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 273 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
202 proxy_port=None): 274 proxy_port=None, private_key_path=None):
203 """Implementation of an actual update. 275 """Implementation of an actual update.
204 276
205 See PerformUpdate for description of args. Subclasses must override this 277 See PerformUpdate for description of args. Subclasses must override this
206 method with the correct update procedure for the class. 278 method with the correct update procedure for the class.
207 """ 279 """
208 pass 280 pass
209 281
210 def _UpdateUsingPayload(self, update_path, stateful_change='old', 282 def _UpdateUsingPayload(self, update_path, stateful_change='old',
211 proxy_port=None): 283 proxy_port=None):
212 """Updates target with the pre-generated update stored in update_path. 284 """Updates target with the pre-generated update stored in update_path.
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after
403 def ProcessOptions(cls, parser, options): 475 def ProcessOptions(cls, parser, options):
404 """Processes non-vm-specific options.""" 476 """Processes non-vm-specific options."""
405 AUTest.ProcessOptions(parser, options) 477 AUTest.ProcessOptions(parser, options)
406 cls.remote = options.remote 478 cls.remote = options.remote
407 479
408 if not cls.remote: 480 if not cls.remote:
409 parser.error('We require a remote address for real tests.') 481 parser.error('We require a remote address for real tests.')
410 482
411 def PrepareBase(self, image_path): 483 def PrepareBase(self, image_path):
412 """Auto-update to base image to prepare for test.""" 484 """Auto-update to base image to prepare for test."""
413 self.PerformUpdate(image_path) 485 _PrepareRealBase(image_path)
414 486
415 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 487 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
416 proxy_port=None): 488 proxy_port=None, private_key_path=None):
417 """Updates a remote image using image_to_live.sh.""" 489 """Updates a remote image using image_to_live.sh."""
418 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 490 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
419 cmd = ['%s/image_to_live.sh' % self.crosutils, 491 cmd = ['%s/image_to_live.sh' % self.crosutils,
420 '--image=%s' % image_path,
421 '--remote=%s' % self.remote, 492 '--remote=%s' % self.remote,
422 stateful_change_flag, 493 stateful_change_flag,
423 '--verify', 494 '--verify',
424 '--src_image=%s' % src_image_path
425 ] 495 ]
426 496 self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port,
427 if proxy_port: 497 private_key_path)
428 cmd.append('--proxy_port=%s' % proxy_port) 498 self.RunUpdateCmd(cmd)
429
430 if self.verbose:
431 try:
432 RunCommand(cmd)
433 except Exception, e:
434 raise UpdateException(1, e.message)
435 else:
436 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
437 if code != 0:
438 raise UpdateException(code, stdout)
439 499
440 def _UpdateUsingPayload(self, update_path, stateful_change='old', 500 def _UpdateUsingPayload(self, update_path, stateful_change='old',
441 proxy_port=None): 501 proxy_port=None):
442 """Updates a remote image using image_to_live.sh.""" 502 """Updates a remote image using image_to_live.sh."""
443 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 503 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
444 cmd = ['%s/image_to_live.sh' % self.crosutils, 504 cmd = ['%s/image_to_live.sh' % self.crosutils,
445 '--payload=%s' % update_path, 505 '--payload=%s' % update_path,
446 '--remote=%s' % self.remote, 506 '--remote=%s' % self.remote,
447 stateful_change_flag, 507 stateful_change_flag,
448 '--verify', 508 '--verify',
449 ] 509 ]
450 510 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
451 if proxy_port: 511 self.RunUpdateCmd(cmd)
452 cmd.append('--proxy_port=%s' % proxy_port)
453
454 if self.verbose:
455 try:
456 RunCommand(cmd)
457 except Exception, e:
458 raise UpdateException(1, e.message)
459 else:
460 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
461 if code != 0:
462 raise UpdateException(code, stdout)
463 512
464 def VerifyImage(self, percent_required_to_pass): 513 def VerifyImage(self, percent_required_to_pass):
465 """Verifies an image using run_remote_tests.sh with verification suite.""" 514 """Verifies an image using run_remote_tests.sh with verification suite."""
466 output = RunCommand([ 515 output = RunCommand([
467 '%s/run_remote_tests.sh' % self.crosutils, 516 '%s/run_remote_tests.sh' % self.crosutils,
468 '--remote=%s' % self.remote, 517 '--remote=%s' % self.remote,
469 self.verify_suite, 518 self.verify_suite,
470 ], error_ok=True, enter_chroot=False, redirect_stdout=True) 519 ], error_ok=True, enter_chroot=False, redirect_stdout=True)
471 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) 520 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
472 521
473 522
474 class VirtualAUTest(unittest.TestCase, AUTest): 523 class VirtualAUTest(unittest.TestCase, AUTest):
475 """Test harness for updating virtual machines.""" 524 """Test harness for updating virtual machines."""
476 525
477 # VM Constants.
478 _FULL_VDISK_SIZE = 6072
479 _FULL_STATEFULFS_SIZE = 3074
480
481 # Class variables used to acquire individual VM variables per test. 526 # Class variables used to acquire individual VM variables per test.
482 _vm_lock = threading.Lock() 527 _vm_lock = threading.Lock()
483 _next_port = 9222 528 _next_port = 9222
484 529
485 def _KillExistingVM(self, pid_file): 530 def _KillExistingVM(self, pid_file):
486 if os.path.exists(pid_file): 531 if os.path.exists(pid_file):
487 Warning('Existing %s found. Deleting and killing process' % 532 Warning('Existing %s found. Deleting and killing process' %
488 pid_file) 533 pid_file)
489 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], 534 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file],
490 cwd=self.crosutilsbin) 535 cwd=self.crosutilsbin)
491 536
492 assert not os.path.exists(pid_file) 537 assert not os.path.exists(pid_file)
493 538
494 def _AcquireUniquePortAndPidFile(self): 539 def _AcquireUniquePortAndPidFile(self):
495 """Acquires unique ssh port and pid file for VM.""" 540 """Acquires unique ssh port and pid file for VM."""
496 with VirtualAUTest._vm_lock: 541 with VirtualAUTest._vm_lock:
497 self._ssh_port = VirtualAUTest._next_port 542 self._ssh_port = VirtualAUTest._next_port
498 self._kvm_pid_file = '/tmp/kvm.%d' % self._ssh_port 543 self._kvm_pid_file = '/tmp/kvm.%d' % self._ssh_port
499 VirtualAUTest._next_port += 1 544 VirtualAUTest._next_port += 1
500 545
501 def setUp(self): 546 def setUp(self):
502 """Unit test overriden method. Is called before every test.""" 547 """Unit test overriden method. Is called before every test."""
503 AUTest.setUp(self) 548 AUTest.setUp(self)
504 self.vm_image_path = None
505 self._AcquireUniquePortAndPidFile() 549 self._AcquireUniquePortAndPidFile()
506 self._KillExistingVM(self._kvm_pid_file) 550 self._KillExistingVM(self._kvm_pid_file)
507 551
508 def tearDown(self): 552 def tearDown(self):
509 self._KillExistingVM(self._kvm_pid_file) 553 self._KillExistingVM(self._kvm_pid_file)
510 554
511 @classmethod 555 @classmethod
512 def ProcessOptions(cls, parser, options): 556 def ProcessOptions(cls, parser, options):
513 """Processes vm-specific options.""" 557 """Processes vm-specific options."""
514 AUTest.ProcessOptions(parser, options) 558 AUTest.ProcessOptions(parser, options)
515 cls.board = options.board
516 559
517 # Communicate flags to tests. 560 # Communicate flags to tests.
518 cls.graphics_flag = '' 561 cls.graphics_flag = ''
519 if options.no_graphics: cls.graphics_flag = '--no_graphics' 562 if options.no_graphics: cls.graphics_flag = '--no_graphics'
520 563 if not cls.board: parser.error('Need board to convert base image to vm.')
521 if not cls.board:
522 parser.error('Need board to convert base image to vm.')
523 564
524 def PrepareBase(self, image_path): 565 def PrepareBase(self, image_path):
525 """Creates an update-able VM based on base image.""" 566 """Creates an update-able VM based on base image."""
526 # Needed for VM delta updates. We need to use the qemu image rather 567 self._PrepareVMBase(image_path)
527 # than the base image on a first update. By tracking the first_update
528 # we can set src_image to the qemu form of the base image when
529 # performing generating the delta payload.
530 self._first_update = True
531 self.vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
532 image_path)
533 if not os.path.exists(self.vm_image_path):
534 Info('Creating %s' % vm_image_path)
535 RunCommand(['%s/image_to_vm.sh' % self.crosutils,
536 '--full',
537 '--from=%s' % ReinterpretPathForChroot(
538 os.path.dirname(image_path)),
539 '--vdisk_size=%s' % self._FULL_VDISK_SIZE,
540 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE,
541 '--board=%s' % self.board,
542 '--test_image'], enter_chroot=True)
543
544 Info('Using %s as base' % self.vm_image_path)
545 self.assertTrue(os.path.exists(self.vm_image_path))
546 568
547 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 569 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
548 proxy_port=''): 570 proxy_port='', private_key_path=None):
549 """Updates VM image with image_path.""" 571 """Updates VM image with image_path."""
550 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 572 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
551 if src_image_path and self._first_update: 573 if src_image_path and self._first_update:
552 src_image_path = self.vm_image_path 574 src_image_path = self.vm_image_path
553 self._first_update = False 575 self._first_update = False
554 576
555 # Check image payload cache first. 577 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
556 update_id = _GenerateUpdateId(target=image_path, src=src_image_path) 578 '--vm_image_path=%s' % self.vm_image_path,
557 cache_path = dev_server_cache[update_id] 579 '--snapshot',
558 if cache_path: 580 self.graphics_flag,
559 Info('Using cache %s' % cache_path) 581 '--persist',
560 update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) 582 '--kvm_pid=%s' % self._kvm_pid_file,
561 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 583 '--ssh_port=%s' % self._ssh_port,
562 '--vm_image_path=%s' % self.vm_image_path, 584 stateful_change_flag,
563 '--snapshot', 585 ]
564 self.graphics_flag,
565 '--persist',
566 '--kvm_pid=%s' % self._kvm_pid_file,
567 '--ssh_port=%s' % self._ssh_port,
568 stateful_change_flag,
569 '--update_url=%s' % update_url,
570 ]
571 else:
572 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
573 '--update_image_path=%s' % image_path,
574 '--vm_image_path=%s' % self.vm_image_path,
575 '--snapshot',
576 self.graphics_flag,
577 '--persist',
578 '--kvm_pid=%s' % self._kvm_pid_file,
579 '--ssh_port=%s' % self._ssh_port,
580 stateful_change_flag,
581 '--src_image=%s' % src_image_path,
582 '--proxy_port=%s' % proxy_port
583 ]
584 586
585 if self.verbose: 587 self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port,
586 try: 588 private_key_path)
587 RunCommand(cmd) 589 self.RunUpdateCmd(cmd)
588 except Exception, e:
589 raise UpdateException(1, e.message)
590 else:
591 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
592 if code != 0:
593 raise UpdateException(code, stdout)
594 590
595 def _UpdateUsingPayload(self, update_path, stateful_change='old', 591 def _UpdateUsingPayload(self, update_path, stateful_change='old',
596 proxy_port=None): 592 proxy_port=None):
597 """Updates a vm image using cros_run_vm_update.""" 593 """Updates a vm image using cros_run_vm_update."""
598 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 594 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
599 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 595 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
600 '--payload=%s' % update_path, 596 '--payload=%s' % update_path,
601 '--vm_image_path=%s' % self.vm_image_path, 597 '--vm_image_path=%s' % self.vm_image_path,
602 '--snapshot', 598 '--snapshot',
603 self.graphics_flag, 599 self.graphics_flag,
604 '--persist', 600 '--persist',
605 '--kvm_pid=%s' % self._kvm_pid_file, 601 '--kvm_pid=%s' % self._kvm_pid_file,
606 '--ssh_port=%s' % self._ssh_port, 602 '--ssh_port=%s' % self._ssh_port,
607 stateful_change_flag, 603 stateful_change_flag,
608 ] 604 ]
609 605 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
610 if proxy_port: 606 self.RunUpdateCmd(cmd)
611 cmd.append('--proxy_port=%s' % proxy_port)
612
613 if self.verbose:
614 try:
615 RunCommand(cmd)
616 except Exception, e:
617 raise UpdateException(1, e.message)
618 else:
619 (code, stdout, stderr) = RunCommandCaptureOutput(cmd)
620 if code != 0:
621 raise UpdateException(code, stdout)
622 607
623 def VerifyImage(self, percent_required_to_pass): 608 def VerifyImage(self, percent_required_to_pass):
624 """Runs vm smoke suite to verify image.""" 609 """Runs vm smoke suite to verify image."""
625 # image_to_live already verifies lsb-release matching. This is just 610 # image_to_live already verifies lsb-release matching. This is just
626 # for additional steps. 611 # for additional steps.
627 612
628 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, 613 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin,
629 '--image_path=%s' % self.vm_image_path, 614 '--image_path=%s' % self.vm_image_path,
630 '--snapshot', 615 '--snapshot',
631 '--persist', 616 '--persist',
632 '--kvm_pid=%s' % self._kvm_pid_file, 617 '--kvm_pid=%s' % self._kvm_pid_file,
633 '--ssh_port=%s' % self._ssh_port, 618 '--ssh_port=%s' % self._ssh_port,
634 self.verify_suite, 619 self.verify_suite,
635 ] 620 ]
636 621
637 if self.graphics_flag: 622 if self.graphics_flag:
638 commandWithArgs.append(self.graphics_flag) 623 commandWithArgs.append(self.graphics_flag)
639 624
640 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, 625 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False,
641 redirect_stdout=True) 626 redirect_stdout=True)
642 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) 627 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
643 628
644 629
645 class GenerateVirtualAUDeltasTest(VirtualAUTest): 630 class PregenerateAUDeltas(unittest.TestCase, AUTest):
646 """Class the overrides VirtualAUTest and stores deltas we will generate.""" 631 """Magical class that emulates an AUTest to store deltas we will generate.
632
633 This class emulates an AUTest such that when it runs as a TestCase it runs
634 through the exact up
635 """
647 delta_list = {} 636 delta_list = {}
648 637
649 def setUp(self): 638 def setUp(self):
650 AUTest.setUp(self) 639 AUTest.setUp(self)
651 640
652 def tearDown(self): 641 def tearDown(self):
653 pass 642 pass
654 643
644 @classmethod
645 def ProcessOptions(cls, parser, options):
646 AUTest.ProcessOptions(parser, options)
647 cls.au_type = options.type
648
649 def PrepareBase(self, image_path):
650 if self.au_type == 'vm':
651 self._PrepareVMBase(image_path)
652 else:
653 self._PrepareRealBase(image_path)
654
655 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 655 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
656 proxy_port=None): 656 proxy_port=None, private_key_path=None):
657 if src_image_path and self._first_update: 657 if self.au_type == 'vm' and src_image_path and self._first_update:
658 src_image_path = self.vm_image_path 658 src_image_path = self.vm_image_path
659 self._first_update = False 659 self._first_update = False
660 660
661 # Generate a value that combines delta with private key path.
662 val = '%s+%s' % (src_image_path, private_key_path)
661 if not self.delta_list.has_key(image_path): 663 if not self.delta_list.has_key(image_path):
662 self.delta_list[image_path] = set([src_image_path]) 664 self.delta_list[image_path] = set([val])
663 else: 665 else:
664 self.delta_list[image_path].add(src_image_path) 666 self.delta_list[image_path].add(val)
665 667
666 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): 668 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg):
667 pass 669 pass
668 670
669 def VerifyImage(self, percent_required_to_pass): 671 def VerifyImage(self, percent_required_to_pass):
670 pass 672 pass
671 673
672 674
673 class ParallelJob(threading.Thread): 675 class ParallelJob(threading.Thread):
674 """Small wrapper for threading. Thread that releases a semaphores on exit.""" 676 """Small wrapper for threading. Thread that releases a semaphores on exit."""
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
749 def GetDevServerURL(cls, port, sub_dir): 751 def GetDevServerURL(cls, port, sub_dir):
750 """Returns the dev server url for a given port and sub directory.""" 752 """Returns the dev server url for a given port and sub directory."""
751 ip_addr = GetIPAddress() 753 ip_addr = GetIPAddress()
752 if not port: port = 8080 754 if not port: port = 8080
753 url = 'http://%(ip)s:%(port)s/%(dir)s' % {'ip': ip_addr, 755 url = 'http://%(ip)s:%(port)s/%(dir)s' % {'ip': ip_addr,
754 'port': str(port), 756 'port': str(port),
755 'dir': sub_dir} 757 'dir': sub_dir}
756 return url 758 return url
757 759
758 760
759 def _GenerateUpdateId(target, src): 761 def _GenerateUpdateId(target, src, key):
760 """Returns a simple representation id of target and src paths.""" 762 """Returns a simple representation id of target and src paths."""
761 if src: 763 update_id = target
762 return '%s->%s' % (target, src) 764 if src: update_id = '->'.join([update_id, src])
763 else: 765 if key: update_id = '+'.join([update_id, key])
764 return target 766 return update_id
765 767
766 768
767 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args, print_status) : 769 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args,
770 print_status):
768 771
769 """Runs set number of specified jobs in parallel. 772 """Runs set number of specified jobs in parallel.
770 773
771 Args: 774 Args:
772 number_of_simultaneous_jobs: Max number of threads to be run in parallel. 775 number_of_simultaneous_jobs: Max number of threads to be run in parallel.
773 jobs: Array of methods to run. 776 jobs: Array of methods to run.
774 jobs_args: Array of args associated with method calls. 777 jobs_args: Array of args associated with method calls.
775 print_status: True if you'd like this to print out .'s as it runs jobs. 778 print_status: True if you'd like this to print out .'s as it runs jobs.
776 Returns: 779 Returns:
777 Returns an array of results corresponding to each thread. 780 Returns an array of results corresponding to each thread.
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
827 This method effectively pre-generates the dev server cache for all tests. 830 This method effectively pre-generates the dev server cache for all tests.
828 831
829 Args: 832 Args:
830 parser: parser from main. 833 parser: parser from main.
831 options: options from parsed parser. 834 options: options from parsed parser.
832 Returns: 835 Returns:
833 Dictionary of Update Identifiers->Relative cache locations. 836 Dictionary of Update Identifiers->Relative cache locations.
834 Raises: 837 Raises:
835 UpdateException if we fail to generate an update. 838 UpdateException if we fail to generate an update.
836 """ 839 """
837 def _GenerateVMUpdate(target, src): 840 def _GenerateVMUpdate(target, src, private_key_path):
838 """Generates an update using the devserver.""" 841 """Generates an update using the devserver."""
839 target = ReinterpretPathForChroot(target) 842 command = ['./enter_chroot.sh',
840 if src: 843 '--nogit_config',
841 src = ReinterpretPathForChroot(src) 844 '--',
845 'sudo',
846 './start_devserver',
847 '--pregenerate_update',
848 '--exit',
849 ]
850 # Add actual args to command.
851 command.append('--image=%s' % ReinterpretPathForChroot(target))
852 if src: command.append('--src_image=%s' % ReinterpretPathForChroot(src))
853 if options.type == 'vm': command.append('--for_vm')
854 if private_key_path:
855 command.append('--private_key=%s' %
856 ReinterpretPathForChroot(private_key_path))
842 857
843 return RunCommandCaptureOutput(['./enter_chroot.sh', 858 return RunCommandCaptureOutput(command, combine_stdout_stderr=True,
844 '--nogit_config', 859 print_cmd=True)
845 '--',
846 'sudo',
847 './start_devserver',
848 '--pregenerate_update',
849 '--exit',
850 '--image=%s' % target,
851 '--src_image=%s' % src,
852 '--for_vm',
853 ], combine_stdout_stderr=True,
854 print_cmd=False)
855 860
856 # Get the list of deltas by mocking out update method in test class. 861 # Get the list of deltas by mocking out update method in test class.
857 test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) 862 test_suite = _PrepareTestSuite(parser, options, PregenerateAUDeltas)
858 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) 863 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite)
864 if not test_result.wasSuccessful():
865 raise UpdateException(1, 'Error finding updates to generate.')
859 866
860 Info('The following delta updates are required.') 867 Info('The following delta updates are required.')
861 update_ids = [] 868 update_ids = []
862 jobs = [] 869 jobs = []
863 args = [] 870 args = []
864 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): 871 for target, srcs in PregenerateAUDeltas.delta_list.items():
865 for src in srcs: 872 for src_key in srcs:
866 update_id = _GenerateUpdateId(target=target, src=src) 873 (src, key) = src_key.split('+')
874 # TODO(sosa): Add private key as part of caching name once devserver can
875 # handle it its own cache.
876 update_id = _GenerateUpdateId(target=target, src=src, key=key)
867 print >> sys.stderr, 'AU: %s' % update_id 877 print >> sys.stderr, 'AU: %s' % update_id
868 update_ids.append(update_id) 878 update_ids.append(update_id)
869 jobs.append(_GenerateVMUpdate) 879 jobs.append(_GenerateVMUpdate)
870 args.append((target, src)) 880 args.append((target, src, key))
871 881
872 raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True) 882 raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True)
873 results = [] 883 results = []
874 884
875 # Looking for this line in the output. 885 # Looking for this line in the output.
876 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') 886 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)')
877 for result in raw_results: 887 for result in raw_results:
878 (return_code, output, _) = result 888 (return_code, output, _) = result
879 if return_code != 0: 889 if return_code != 0:
880 Warning(output) 890 Warning(output)
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
912 test_case = unittest.TestLoader().loadTestsFromName(test_name) 922 test_case = unittest.TestLoader().loadTestsFromName(test_name)
913 threads.append(unittest.TextTestRunner().run) 923 threads.append(unittest.TextTestRunner().run)
914 args.append(test_case) 924 args.append(test_case)
915 925
916 results = _RunParallelJobs(options.jobs, threads, args, print_status=False) 926 results = _RunParallelJobs(options.jobs, threads, args, print_status=False)
917 for test_result in results: 927 for test_result in results:
918 if not test_result.wasSuccessful(): 928 if not test_result.wasSuccessful():
919 Die('Test harness was not successful') 929 Die('Test harness was not successful')
920 930
921 931
932 def InsertPublicKeyIntoImage(image_path, key_path):
933 """Inserts public key into image @ static update_engine location."""
934 from_dir = os.path.dirname(image_path)
935 image = os.path.basename(image_path)
936 crosutils_dir = os.path.abspath(__file__).rsplit('/', 2)[0]
937 target_key_path = 'usr/share/update_engine/update-payload-key.pub.pem'
938
939 # Temporary directories for this function.
940 rootfs_dir = tempfile.mkdtemp(suffix='rootfs', prefix='tmp')
941 stateful_dir = tempfile.mkdtemp(suffix='stateful', prefix='tmp')
942
943 Info('Copying %s into %s' % (key_path, image_path))
944 try:
945 RunCommand(['./mount_gpt_image.sh',
946 '--from=%s' % from_dir,
947 '--image=%s' % image,
948 '--rootfs_mountpt=%s' % rootfs_dir,
949 '--stateful_mountpt=%s' % stateful_dir,
950 ], print_cmd=False, redirect_stdout=True,
951 redirect_stderr=True, cwd=crosutils_dir)
952 path = os.path.join(rootfs_dir, target_key_path)
953 dir_path = os.path.dirname(path)
954 RunCommand(['sudo', 'mkdir', '--parents', dir_path], print_cmd=False)
955 RunCommand(['sudo', 'cp', '--force', '-p', key_path, path],
956 print_cmd=False)
957 finally:
958 # Unmount best effort regardless.
959 RunCommand(['./mount_gpt_image.sh',
960 '--unmount',
961 '--rootfs_mountpt=%s' % rootfs_dir,
962 '--stateful_mountpt=%s' % stateful_dir,
963 ], print_cmd=False, redirect_stdout=True, redirect_stderr=True,
964 cwd=crosutils_dir)
965 RunCommand(['bin/cros_make_image_bootable', from_dir, image, ],
dgarrett 2011/02/11 01:25:55 Should this be in the finally clause, or unindente
sosa 2011/02/11 21:20:02 Done.
966 print_cmd=False, redirect_stdout=True, redirect_stderr=True,
967 enter_chroot=True, cwd=crosutils_dir)
968 # Clean up our directories.
969 os.rmdir(rootfs_dir)
970 os.rmdir(stateful_dir)
971
972
973 def CleanPreviousWork(options):
974 """Cleans up previous work from the devserver cache and local image cache."""
975 Info('Cleaning up previous work.')
976 # Wipe devserver cache.
977 RunCommandCaptureOutput(
978 ['sudo', './start_devserver', '--clear_cache', '--exit', ],
979 enter_chroot=True, print_cmd=False, combine_stdout_stderr=True)
980
981 # Clean previous vm images if they exist.
982 if options.type == 'vm':
983 target_vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
984 options.target_image)
985 base_vm_image_path = '%s/chromiumos_qemu_image.bin' % os.path.dirname(
986 options.base_image)
987 if os.path.exists(target_vm_image_path): os.remove(target_vm_image_path)
988 if os.path.exists(base_vm_image_path): os.remove(base_vm_image_path)
989
990
922 def main(): 991 def main():
923 parser = optparse.OptionParser() 992 parser = optparse.OptionParser()
924 parser.add_option('-b', '--base_image', 993 parser.add_option('-b', '--base_image',
925 help='path to the base image.') 994 help='path to the base image.')
926 parser.add_option('-r', '--board', 995 parser.add_option('-r', '--board',
927 help='board for the images.') 996 help='board for the images.')
997 parser.add_option('--clean', default=False, dest='clean', action='store_true',
998 help='Clean all previous state')
928 parser.add_option('--no_delta', action='store_false', default=True, 999 parser.add_option('--no_delta', action='store_false', default=True,
929 dest='delta', 1000 dest='delta',
930 help='Disable using delta updates.') 1001 help='Disable using delta updates.')
931 parser.add_option('--no_graphics', action='store_true', 1002 parser.add_option('--no_graphics', action='store_true',
932 help='Disable graphics for the vm test.') 1003 help='Disable graphics for the vm test.')
933 parser.add_option('-j', '--jobs', default=8, type=int, 1004 parser.add_option('-j', '--jobs', default=8, type=int,
934 help='Number of simultaneous jobs') 1005 help='Number of simultaneous jobs')
1006 parser.add_option('--public_key', default=None,
1007 help='Public key to use on images and updates.')
1008 parser.add_option('--private_key', default=None,
1009 help='Private key to use on images and updates.')
935 parser.add_option('-q', '--quick_test', default=False, action='store_true', 1010 parser.add_option('-q', '--quick_test', default=False, action='store_true',
936 help='Use a basic test to verify image.') 1011 help='Use a basic test to verify image.')
937 parser.add_option('-m', '--remote', 1012 parser.add_option('-m', '--remote',
938 help='Remote address for real test.') 1013 help='Remote address for real test.')
939 parser.add_option('-t', '--target_image', 1014 parser.add_option('-t', '--target_image',
940 help='path to the target image.') 1015 help='path to the target image.')
941 parser.add_option('--test_prefix', default='test', 1016 parser.add_option('--test_prefix', default='test',
942 help='Only runs tests with specific prefix i.e. ' 1017 help='Only runs tests with specific prefix i.e. '
943 'testFullUpdateWipeStateful.') 1018 'testFullUpdateWipeStateful.')
944 parser.add_option('-p', '--type', default='vm', 1019 parser.add_option('-p', '--type', default='vm',
945 help='type of test to run: [vm, real]. Default: vm.') 1020 help='type of test to run: [vm, real]. Default: vm.')
946 parser.add_option('--verbose', default=True, action='store_true', 1021 parser.add_option('--verbose', default=True, action='store_true',
947 help='Print out rather than capture output as much as ' 1022 help='Print out rather than capture output as much as '
948 'possible.') 1023 'possible.')
949 (options, leftover_args) = parser.parse_args() 1024 (options, leftover_args) = parser.parse_args()
950 1025
951 if leftover_args: 1026 if leftover_args: parser.error('Found unsupported flags: %s' % leftover_args)
952 parser.error('Found extra options we do not support: %s' % leftover_args) 1027
1028 assert options.target_image and os.path.exists(options.target_image), \
1029 'Target image path does not exist'
1030 if not options.base_image:
1031 Info('Base image not specified. Using target image as base image.')
1032 options.base_image = options.target_image
1033
1034 # Sanity checks on keys and insert them onto the image. The caches must be
1035 # cleaned so we know that the vm images and payloads match the possibly new
1036 # key.
1037 if options.private_key or options.public_key:
dgarrett 2011/02/11 01:25:55 If either is specified, you verify that both exist
sosa 2011/02/11 21:20:02 Done.
1038 assert os.path.exists(options.private_key), 'Could not find private key.'
1039 assert os.path.exists(options.public_key), 'Could not find public key.'
1040 InsertPublicKeyIntoImage(options.target_image, options.public_key)
1041 InsertPublicKeyIntoImage(options.base_image, options.public_key)
1042 options.clean = True
1043
1044 # Clean up previous work if were requested to.
dgarrett 2011/02/11 01:25:55 s/were/we're/
sosa 2011/02/11 21:20:02 Done.
1045 if options.clean: CleanPreviousWork(options)
953 1046
954 # Figure out the test_class. 1047 # Figure out the test_class.
955 if options.type == 'vm': test_class = VirtualAUTest 1048 if options.type == 'vm': test_class = VirtualAUTest
956 elif options.type == 'real': test_class = RealAUTest 1049 elif options.type == 'real': test_class = RealAUTest
957 else: parser.error('Could not parse harness type %s.' % options.type) 1050 else: parser.error('Could not parse harness type %s.' % options.type)
958 1051
959 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). 1052 # Generate cache of updates to use during test harness.
960 global dev_server_cache 1053 global dev_server_cache
961 if options.type == 'vm' and options.jobs > 1: 1054 dev_server_cache = _PregenerateUpdates(parser, options)
962 dev_server_cache = _PregenerateUpdates(parser, options) 1055 my_server = DevServerWrapper()
963 my_server = DevServerWrapper() 1056 my_server.start()
964 my_server.start() 1057 try:
965 try: 1058 if options.type == 'vm':
966 _RunTestsInParallel(parser, options, test_class) 1059 _RunTestsInParallel(parser, options, test_class)
967 finally: 1060 else:
968 my_server.Stop() 1061 # TODO(sosa) - Take in a machine pool for a real test.
969 1062 # Can't run in parallel with only one remote device.
970 else: 1063 test_suite = _PrepareTestSuite(parser, options, test_class)
971 dev_server_cache = None 1064 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite)
972 test_suite = _PrepareTestSuite(parser, options, test_class) 1065 if not test_result.wasSuccessful(): Die('Test harness failed.')
973 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) 1066 finally:
974 if not test_result.wasSuccessful(): 1067 my_server.Stop()
975 Die('Test harness was not successful.')
976 1068
977 1069
978 if __name__ == '__main__': 1070 if __name__ == '__main__':
979 main() 1071 main()
OLDNEW
« no previous file with comments | « no previous file | bin/cros_run_vm_update » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698