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

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: nits 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):
sosa 2011/02/10 06:14:35 These two are moved out so we can call them in the
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
sosa 2011/02/10 06:14:35 Allows you to override so you can specify a bad ke
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
181 if options.quick_test: 252 if options.quick_test:
182 cls.verify_suite = 'build_RootFilesystemSize' 253 cls.verify_suite = 'build_RootFilesystemSize'
183 else: 254 else:
184 cls.verify_suite = 'suite_Smoke' 255 cls.verify_suite = 'suite_Smoke'
185 256
186 # Sanity checks. 257 # Sanity checks.
187 if not cls.base_image_path: 258 if not cls.base_image_path:
188 parser.error('Need path to base image for vm.') 259 parser.error('Need path to base image for vm.')
189 elif not os.path.exists(cls.base_image_path): 260 elif not os.path.exists(cls.base_image_path):
190 Die('%s does not exist' % cls.base_image_path) 261 Die('%s does not exist' % cls.base_image_path)
191 262
192 if not cls.target_image_path: 263 if not cls.target_image_path:
193 parser.error('Need path to target image to update with.') 264 parser.error('Need path to target image to update with.')
194 elif not os.path.exists(cls.target_image_path): 265 elif not os.path.exists(cls.target_image_path):
195 Die('%s does not exist' % cls.target_image_path) 266 Die('%s does not exist' % cls.target_image_path)
196 267
197 def PrepareBase(self, image_path): 268 def PrepareBase(self, image_path):
198 """Prepares target with base_image_path.""" 269 """Prepares target with base_image_path."""
199 pass 270 pass
200 271
201 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 272 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
202 proxy_port=None): 273 proxy_port=None, private_key_path=None):
203 """Implementation of an actual update. 274 """Implementation of an actual update.
204 275
205 See PerformUpdate for description of args. Subclasses must override this 276 See PerformUpdate for description of args. Subclasses must override this
206 method with the correct update procedure for the class. 277 method with the correct update procedure for the class.
207 """ 278 """
208 pass 279 pass
209 280
210 def _UpdateUsingPayload(self, update_path, stateful_change='old', 281 def _UpdateUsingPayload(self, update_path, stateful_change='old',
211 proxy_port=None): 282 proxy_port=None):
212 """Updates target with the pre-generated update stored in update_path. 283 """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): 474 def ProcessOptions(cls, parser, options):
404 """Processes non-vm-specific options.""" 475 """Processes non-vm-specific options."""
405 AUTest.ProcessOptions(parser, options) 476 AUTest.ProcessOptions(parser, options)
406 cls.remote = options.remote 477 cls.remote = options.remote
407 478
408 if not cls.remote: 479 if not cls.remote:
409 parser.error('We require a remote address for real tests.') 480 parser.error('We require a remote address for real tests.')
410 481
411 def PrepareBase(self, image_path): 482 def PrepareBase(self, image_path):
412 """Auto-update to base image to prepare for test.""" 483 """Auto-update to base image to prepare for test."""
413 self.PerformUpdate(image_path) 484 _PrepareRealBase(image_path)
414 485
415 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 486 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
416 proxy_port=None): 487 proxy_port=None, private_key_path=None):
417 """Updates a remote image using image_to_live.sh.""" 488 """Updates a remote image using image_to_live.sh."""
418 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 489 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
419 cmd = ['%s/image_to_live.sh' % self.crosutils, 490 cmd = ['%s/image_to_live.sh' % self.crosutils,
420 '--image=%s' % image_path,
421 '--remote=%s' % self.remote, 491 '--remote=%s' % self.remote,
422 stateful_change_flag, 492 stateful_change_flag,
423 '--verify', 493 '--verify',
424 '--src_image=%s' % src_image_path
425 ] 494 ]
426 495 self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port,
427 if proxy_port: 496 private_key_path)
428 cmd.append('--proxy_port=%s' % proxy_port) 497 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 498
440 def _UpdateUsingPayload(self, update_path, stateful_change='old', 499 def _UpdateUsingPayload(self, update_path, stateful_change='old',
441 proxy_port=None): 500 proxy_port=None):
442 """Updates a remote image using image_to_live.sh.""" 501 """Updates a remote image using image_to_live.sh."""
443 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 502 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
444 cmd = ['%s/image_to_live.sh' % self.crosutils, 503 cmd = ['%s/image_to_live.sh' % self.crosutils,
445 '--payload=%s' % update_path, 504 '--payload=%s' % update_path,
446 '--remote=%s' % self.remote, 505 '--remote=%s' % self.remote,
447 stateful_change_flag, 506 stateful_change_flag,
448 '--verify', 507 '--verify',
449 ] 508 ]
450 509 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
451 if proxy_port: 510 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 511
464 def VerifyImage(self, percent_required_to_pass): 512 def VerifyImage(self, percent_required_to_pass):
465 """Verifies an image using run_remote_tests.sh with verification suite.""" 513 """Verifies an image using run_remote_tests.sh with verification suite."""
466 output = RunCommand([ 514 output = RunCommand([
467 '%s/run_remote_tests.sh' % self.crosutils, 515 '%s/run_remote_tests.sh' % self.crosutils,
468 '--remote=%s' % self.remote, 516 '--remote=%s' % self.remote,
469 self.verify_suite, 517 self.verify_suite,
470 ], error_ok=True, enter_chroot=False, redirect_stdout=True) 518 ], error_ok=True, enter_chroot=False, redirect_stdout=True)
471 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) 519 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
472 520
473 521
474 class VirtualAUTest(unittest.TestCase, AUTest): 522 class VirtualAUTest(unittest.TestCase, AUTest):
475 """Test harness for updating virtual machines.""" 523 """Test harness for updating virtual machines."""
476 524
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. 525 # Class variables used to acquire individual VM variables per test.
482 _vm_lock = threading.Lock() 526 _vm_lock = threading.Lock()
483 _next_port = 9222 527 _next_port = 9222
484 528
485 def _KillExistingVM(self, pid_file): 529 def _KillExistingVM(self, pid_file):
486 if os.path.exists(pid_file): 530 if os.path.exists(pid_file):
487 Warning('Existing %s found. Deleting and killing process' % 531 Warning('Existing %s found. Deleting and killing process' %
488 pid_file) 532 pid_file)
489 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], 533 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file],
490 cwd=self.crosutilsbin) 534 cwd=self.crosutilsbin)
491 535
492 assert not os.path.exists(pid_file) 536 assert not os.path.exists(pid_file)
493 537
494 def _AcquireUniquePortAndPidFile(self): 538 def _AcquireUniquePortAndPidFile(self):
495 """Acquires unique ssh port and pid file for VM.""" 539 """Acquires unique ssh port and pid file for VM."""
496 with VirtualAUTest._vm_lock: 540 with VirtualAUTest._vm_lock:
497 self._ssh_port = VirtualAUTest._next_port 541 self._ssh_port = VirtualAUTest._next_port
498 self._kvm_pid_file = '/tmp/kvm.%d' % self._ssh_port 542 self._kvm_pid_file = '/tmp/kvm.%d' % self._ssh_port
499 VirtualAUTest._next_port += 1 543 VirtualAUTest._next_port += 1
500 544
501 def setUp(self): 545 def setUp(self):
502 """Unit test overriden method. Is called before every test.""" 546 """Unit test overriden method. Is called before every test."""
503 AUTest.setUp(self) 547 AUTest.setUp(self)
504 self.vm_image_path = None
505 self._AcquireUniquePortAndPidFile() 548 self._AcquireUniquePortAndPidFile()
506 self._KillExistingVM(self._kvm_pid_file) 549 self._KillExistingVM(self._kvm_pid_file)
507 550
508 def tearDown(self): 551 def tearDown(self):
509 self._KillExistingVM(self._kvm_pid_file) 552 self._KillExistingVM(self._kvm_pid_file)
510 553
511 @classmethod 554 @classmethod
512 def ProcessOptions(cls, parser, options): 555 def ProcessOptions(cls, parser, options):
513 """Processes vm-specific options.""" 556 """Processes vm-specific options."""
514 AUTest.ProcessOptions(parser, options) 557 AUTest.ProcessOptions(parser, options)
515 cls.board = options.board
516 558
517 # Communicate flags to tests. 559 # Communicate flags to tests.
518 cls.graphics_flag = '' 560 cls.graphics_flag = ''
519 if options.no_graphics: cls.graphics_flag = '--no_graphics' 561 if options.no_graphics: cls.graphics_flag = '--no_graphics'
520 562 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 563
524 def PrepareBase(self, image_path): 564 def PrepareBase(self, image_path):
525 """Creates an update-able VM based on base image.""" 565 """Creates an update-able VM based on base image."""
526 # Needed for VM delta updates. We need to use the qemu image rather 566 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 567
547 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 568 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
548 proxy_port=''): 569 proxy_port='', private_key_path=None):
549 """Updates VM image with image_path.""" 570 """Updates VM image with image_path."""
550 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 571 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
551 if src_image_path and self._first_update: 572 if src_image_path and self._first_update:
552 src_image_path = self.vm_image_path 573 src_image_path = self.vm_image_path
553 self._first_update = False 574 self._first_update = False
554 575
555 # Check image payload cache first. 576 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
556 update_id = _GenerateUpdateId(target=image_path, src=src_image_path) 577 '--vm_image_path=%s' % self.vm_image_path,
557 cache_path = dev_server_cache[update_id] 578 '--snapshot',
558 if cache_path: 579 self.graphics_flag,
559 Info('Using cache %s' % cache_path) 580 '--persist',
560 update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) 581 '--kvm_pid=%s' % self._kvm_pid_file,
561 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 582 '--ssh_port=%s' % self._ssh_port,
562 '--vm_image_path=%s' % self.vm_image_path, 583 stateful_change_flag,
563 '--snapshot', 584 ]
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 585
585 if self.verbose: 586 self.AppendUpdateFlags(cmd, image_path, src_image_path, proxy_port,
586 try: 587 private_key_path)
587 RunCommand(cmd) 588 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 589
595 def _UpdateUsingPayload(self, update_path, stateful_change='old', 590 def _UpdateUsingPayload(self, update_path, stateful_change='old',
596 proxy_port=None): 591 proxy_port=None):
597 """Updates a vm image using cros_run_vm_update.""" 592 """Updates a vm image using cros_run_vm_update."""
598 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) 593 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change)
599 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, 594 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin,
600 '--payload=%s' % update_path, 595 '--payload=%s' % update_path,
601 '--vm_image_path=%s' % self.vm_image_path, 596 '--vm_image_path=%s' % self.vm_image_path,
602 '--snapshot', 597 '--snapshot',
603 self.graphics_flag, 598 self.graphics_flag,
604 '--persist', 599 '--persist',
605 '--kvm_pid=%s' % self._kvm_pid_file, 600 '--kvm_pid=%s' % self._kvm_pid_file,
606 '--ssh_port=%s' % self._ssh_port, 601 '--ssh_port=%s' % self._ssh_port,
607 stateful_change_flag, 602 stateful_change_flag,
608 ] 603 ]
609 604 if proxy_port: cmd.append('--proxy_port=%s' % proxy_port)
610 if proxy_port: 605 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 606
623 def VerifyImage(self, percent_required_to_pass): 607 def VerifyImage(self, percent_required_to_pass):
624 """Runs vm smoke suite to verify image.""" 608 """Runs vm smoke suite to verify image."""
625 # image_to_live already verifies lsb-release matching. This is just 609 # image_to_live already verifies lsb-release matching. This is just
626 # for additional steps. 610 # for additional steps.
627 611
628 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, 612 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin,
629 '--image_path=%s' % self.vm_image_path, 613 '--image_path=%s' % self.vm_image_path,
630 '--snapshot', 614 '--snapshot',
631 '--persist', 615 '--persist',
632 '--kvm_pid=%s' % self._kvm_pid_file, 616 '--kvm_pid=%s' % self._kvm_pid_file,
633 '--ssh_port=%s' % self._ssh_port, 617 '--ssh_port=%s' % self._ssh_port,
634 self.verify_suite, 618 self.verify_suite,
635 ] 619 ]
636 620
637 if self.graphics_flag: 621 if self.graphics_flag:
638 commandWithArgs.append(self.graphics_flag) 622 commandWithArgs.append(self.graphics_flag)
639 623
640 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, 624 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False,
641 redirect_stdout=True) 625 redirect_stdout=True)
642 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) 626 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass)
643 627
644 628
645 class GenerateVirtualAUDeltasTest(VirtualAUTest): 629 class PregenerateAUDeltas(unittest.TestCase, AUTest):
646 """Class the overrides VirtualAUTest and stores deltas we will generate.""" 630 """Magical class that emulates an AUTest to store deltas we will generate.
631
632 This class emulates an AUTest such that when it runs as a TestCase it runs
633 through the exact up
634 """
647 delta_list = {} 635 delta_list = {}
648 636
649 def setUp(self): 637 def setUp(self):
650 AUTest.setUp(self) 638 AUTest.setUp(self)
651 639
652 def tearDown(self): 640 def tearDown(self):
653 pass 641 pass
654 642
643 @classmethod
644 def ProcessOptions(cls, parser, options):
645 AUTest.ProcessOptions(parser, options)
646 cls.au_type = options.type
647
648 def PrepareBase(self, image_path):
649 if self.au_type == 'vm':
650 self._PrepareVMBase(image_path)
651 else:
652 self._PrepareRealBase(image_path)
653
655 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', 654 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old',
656 proxy_port=None): 655 proxy_port=None, private_key_path=None):
657 if src_image_path and self._first_update: 656 if self.au_type == 'vm' and src_image_path and self._first_update:
658 src_image_path = self.vm_image_path 657 src_image_path = self.vm_image_path
659 self._first_update = False 658 self._first_update = False
660 659
660 # Generate a value that combines delta with private key path.
661 val = '%s+%s' % (src_image_path, private_key_path)
661 if not self.delta_list.has_key(image_path): 662 if not self.delta_list.has_key(image_path):
662 self.delta_list[image_path] = set([src_image_path]) 663 self.delta_list[image_path] = set([val])
663 else: 664 else:
664 self.delta_list[image_path].add(src_image_path) 665 self.delta_list[image_path].add(val)
665 666
666 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): 667 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg):
667 pass 668 pass
668 669
669 def VerifyImage(self, percent_required_to_pass): 670 def VerifyImage(self, percent_required_to_pass):
670 pass 671 pass
671 672
672 673
673 class ParallelJob(threading.Thread): 674 class ParallelJob(threading.Thread):
674 """Small wrapper for threading. Thread that releases a semaphores on exit.""" 675 """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): 750 def GetDevServerURL(cls, port, sub_dir):
750 """Returns the dev server url for a given port and sub directory.""" 751 """Returns the dev server url for a given port and sub directory."""
751 ip_addr = GetIPAddress() 752 ip_addr = GetIPAddress()
752 if not port: port = 8080 753 if not port: port = 8080
753 url = 'http://%(ip)s:%(port)s/%(dir)s' % {'ip': ip_addr, 754 url = 'http://%(ip)s:%(port)s/%(dir)s' % {'ip': ip_addr,
754 'port': str(port), 755 'port': str(port),
755 'dir': sub_dir} 756 'dir': sub_dir}
756 return url 757 return url
757 758
758 759
759 def _GenerateUpdateId(target, src): 760 def _GenerateUpdateId(target, src, key):
760 """Returns a simple representation id of target and src paths.""" 761 """Returns a simple representation id of target and src paths."""
761 if src: 762 update_id = target
762 return '%s->%s' % (target, src) 763 if src: update_id = '->'.join([update_id, src])
763 else: 764 if key: update_id = '+'.join([update_id, key])
764 return target 765 return update_id
765 766
766 767
767 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args, print_status) : 768 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args,
769 print_status):
768 770
769 """Runs set number of specified jobs in parallel. 771 """Runs set number of specified jobs in parallel.
770 772
771 Args: 773 Args:
772 number_of_simultaneous_jobs: Max number of threads to be run in parallel. 774 number_of_simultaneous_jobs: Max number of threads to be run in parallel.
773 jobs: Array of methods to run. 775 jobs: Array of methods to run.
774 jobs_args: Array of args associated with method calls. 776 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. 777 print_status: True if you'd like this to print out .'s as it runs jobs.
776 Returns: 778 Returns:
777 Returns an array of results corresponding to each thread. 779 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. 829 This method effectively pre-generates the dev server cache for all tests.
828 830
829 Args: 831 Args:
830 parser: parser from main. 832 parser: parser from main.
831 options: options from parsed parser. 833 options: options from parsed parser.
832 Returns: 834 Returns:
833 Dictionary of Update Identifiers->Relative cache locations. 835 Dictionary of Update Identifiers->Relative cache locations.
834 Raises: 836 Raises:
835 UpdateException if we fail to generate an update. 837 UpdateException if we fail to generate an update.
836 """ 838 """
837 def _GenerateVMUpdate(target, src): 839 def _GenerateVMUpdate(target, src, private_key_path):
838 """Generates an update using the devserver.""" 840 """Generates an update using the devserver."""
839 target = ReinterpretPathForChroot(target) 841 command = ['./enter_chroot.sh',
sosa 2011/02/10 06:14:35 Just a refactor to just append rather than +
840 if src: 842 '--nogit_config',
841 src = ReinterpretPathForChroot(src) 843 '--',
844 'sudo',
845 './start_devserver',
846 '--pregenerate_update',
847 '--exit',
848 ]
849 # Add actual args to command.
850 command.append('--image=%s' % ReinterpretPathForChroot(target))
851 if src: command.append('--src_image=%s' % ReinterpretPathForChroot(src))
852 if options.type == 'vm': command.append('--for_vm')
853 if private_key_path:
854 command.append('--private_key=%s' %
855 ReinterpretPathForChroot(private_key_path))
842 856
843 return RunCommandCaptureOutput(['./enter_chroot.sh', 857 return RunCommandCaptureOutput(command, combine_stdout_stderr=True,
844 '--nogit_config', 858 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 859
856 # Get the list of deltas by mocking out update method in test class. 860 # Get the list of deltas by mocking out update method in test class.
857 test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) 861 test_suite = _PrepareTestSuite(parser, options, PregenerateAUDeltas)
858 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) 862 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite)
863 if not test_result.wasSuccessful():
sosa 2011/02/10 06:14:35 This was a bug being missing.
864 raise UpdateException(1, 'Error finding updates to generate.')
859 865
860 Info('The following delta updates are required.') 866 Info('The following delta updates are required.')
861 update_ids = [] 867 update_ids = []
862 jobs = [] 868 jobs = []
863 args = [] 869 args = []
864 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): 870 for target, srcs in PregenerateAUDeltas.delta_list.items():
865 for src in srcs: 871 for src_key in srcs:
866 update_id = _GenerateUpdateId(target=target, src=src) 872 (src, key) = src_key.split('+')
873 # TODO(sosa): Add private key as part of caching name once devserver can
874 # handle it its own cache.
875 update_id = _GenerateUpdateId(target=target, src=src, key=key)
867 print >> sys.stderr, 'AU: %s' % update_id 876 print >> sys.stderr, 'AU: %s' % update_id
868 update_ids.append(update_id) 877 update_ids.append(update_id)
869 jobs.append(_GenerateVMUpdate) 878 jobs.append(_GenerateVMUpdate)
870 args.append((target, src)) 879 args.append((target, src, key))
871 880
872 raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True) 881 raw_results = _RunParallelJobs(options.jobs, jobs, args, print_status=True)
873 results = [] 882 results = []
874 883
875 # Looking for this line in the output. 884 # Looking for this line in the output.
876 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') 885 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)')
877 for result in raw_results: 886 for result in raw_results:
878 (return_code, output, _) = result 887 (return_code, output, _) = result
879 if return_code != 0: 888 if return_code != 0:
880 Warning(output) 889 Warning(output)
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
912 test_case = unittest.TestLoader().loadTestsFromName(test_name) 921 test_case = unittest.TestLoader().loadTestsFromName(test_name)
913 threads.append(unittest.TextTestRunner().run) 922 threads.append(unittest.TextTestRunner().run)
914 args.append(test_case) 923 args.append(test_case)
915 924
916 results = _RunParallelJobs(options.jobs, threads, args, print_status=False) 925 results = _RunParallelJobs(options.jobs, threads, args, print_status=False)
917 for test_result in results: 926 for test_result in results:
918 if not test_result.wasSuccessful(): 927 if not test_result.wasSuccessful():
919 Die('Test harness was not successful') 928 Die('Test harness was not successful')
920 929
921 930
931 def _InsertPrivateKeyIntoImage(image_path, private_key_path):
sosa 2011/02/10 06:14:35 Core component that adds the public key in the rig
932 from_dir = os.path.dirname(image_path)
933 image = os.path.basename(image_path)
934 crosutils_dir = os.path.abspath(__file__).rsplit('/', 2)[0]
935 target_key_path = 'usr/share/update_engine/update-payload-key.pub.pem'
936
937 # Temporary directories for this function.
938 rootfs_dir = tempfile.mkdtemp(suffix='rootfs', prefix='tmp')
939 stateful_dir = tempfile.mkdtemp(suffix='stateful', prefix='tmp')
940
941 Info('Copying %s into %s' % (private_key_path, image_path))
942 try:
943 RunCommand(['%s/mount_gpt_image.sh' % crosutils_dir,
944 '--from=%s' % from_dir,
945 '--image=%s' % image,
946 '--rootfs_mountpt=%s' % rootfs_dir,
947 '--stateful_mountpt=%s' % stateful_dir,
948 ], print_cmd=False, redirect_stdout=True,
949 redirect_stderr=True)
950 path = os.path.join(rootfs_dir, target_key_path)
951 dir_path = os.path.dirname(path)
952 RunCommand(['sudo', 'mkdir', '--parents', dir_path], print_cmd=False)
953 RunCommand(['sudo', 'cp', '--force', '-p', private_key_path, path],
954 print_cmd=False)
955 finally:
956 # Unmount best effort regardless.
957 RunCommand(['%s/mount_gpt_image.sh' % crosutils_dir,
958 '--unmount',
959 '--rootfs_mountpt=%s' % rootfs_dir,
960 '--stateful_mountpt=%s' % stateful_dir,
961 ], print_cmd=False, redirect_stdout=True, redirect_stderr=True)
962 # Clean up our directories.
963 os.rmdir(rootfs_dir)
964 os.rmdir(stateful_dir)
965
966
922 def main(): 967 def main():
923 parser = optparse.OptionParser() 968 parser = optparse.OptionParser()
924 parser.add_option('-b', '--base_image', 969 parser.add_option('-b', '--base_image',
925 help='path to the base image.') 970 help='path to the base image.')
926 parser.add_option('-r', '--board', 971 parser.add_option('-r', '--board',
927 help='board for the images.') 972 help='board for the images.')
928 parser.add_option('--no_delta', action='store_false', default=True, 973 parser.add_option('--no_delta', action='store_false', default=True,
929 dest='delta', 974 dest='delta',
930 help='Disable using delta updates.') 975 help='Disable using delta updates.')
931 parser.add_option('--no_graphics', action='store_true', 976 parser.add_option('--no_graphics', action='store_true',
932 help='Disable graphics for the vm test.') 977 help='Disable graphics for the vm test.')
933 parser.add_option('-j', '--jobs', default=8, type=int, 978 parser.add_option('-j', '--jobs', default=8, type=int,
934 help='Number of simultaneous jobs') 979 help='Number of simultaneous jobs')
980 parser.add_option('--public_key', default=None,
981 help='Public key to use on images and updates.')
982 parser.add_option('--private_key', default=None,
983 help='Private key to use on images and updates.')
935 parser.add_option('-q', '--quick_test', default=False, action='store_true', 984 parser.add_option('-q', '--quick_test', default=False, action='store_true',
936 help='Use a basic test to verify image.') 985 help='Use a basic test to verify image.')
937 parser.add_option('-m', '--remote', 986 parser.add_option('-m', '--remote',
938 help='Remote address for real test.') 987 help='Remote address for real test.')
939 parser.add_option('-t', '--target_image', 988 parser.add_option('-t', '--target_image',
940 help='path to the target image.') 989 help='path to the target image.')
941 parser.add_option('--test_prefix', default='test', 990 parser.add_option('--test_prefix', default='test',
942 help='Only runs tests with specific prefix i.e. ' 991 help='Only runs tests with specific prefix i.e. '
943 'testFullUpdateWipeStateful.') 992 'testFullUpdateWipeStateful.')
944 parser.add_option('-p', '--type', default='vm', 993 parser.add_option('-p', '--type', default='vm',
945 help='type of test to run: [vm, real]. Default: vm.') 994 help='type of test to run: [vm, real]. Default: vm.')
946 parser.add_option('--verbose', default=True, action='store_true', 995 parser.add_option('--verbose', default=True, action='store_true',
947 help='Print out rather than capture output as much as ' 996 help='Print out rather than capture output as much as '
948 'possible.') 997 'possible.')
949 (options, leftover_args) = parser.parse_args() 998 (options, leftover_args) = parser.parse_args()
950 999
951 if leftover_args: 1000 if leftover_args:
952 parser.error('Found extra options we do not support: %s' % leftover_args) 1001 parser.error('Found extra options we do not support: %s' % leftover_args)
953 1002
1003 assert options.target_image and os.path.exists(options.target_image), \
1004 'Target image path does not exist'
1005 if not options.base_image:
1006 Warning('Base image not specified. Using target image as base image.')
1007 options.base_image = options.target_image
1008
954 # Figure out the test_class. 1009 # Figure out the test_class.
955 if options.type == 'vm': test_class = VirtualAUTest 1010 if options.type == 'vm': test_class = VirtualAUTest
956 elif options.type == 'real': test_class = RealAUTest 1011 elif options.type == 'real': test_class = RealAUTest
957 else: parser.error('Could not parse harness type %s.' % options.type) 1012 else: parser.error('Could not parse harness type %s.' % options.type)
958 1013
1014 if options.private_key or options.public_key:
1015 assert os.path.exists(options.private_key), 'Could not find private key.'
1016 assert os.path.exists(options.public_key), 'Could not find public key.'
1017 _InsertPrivateKeyIntoImage(options.target_image, options.public_key)
1018 _InsertPrivateKeyIntoImage(options.base_image, options.public_key)
1019
959 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). 1020 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet).
960 global dev_server_cache 1021 global dev_server_cache
961 if options.type == 'vm' and options.jobs > 1: 1022 if options.type == 'vm' and options.jobs > 1:
962 dev_server_cache = _PregenerateUpdates(parser, options) 1023 dev_server_cache = _PregenerateUpdates(parser, options)
963 my_server = DevServerWrapper() 1024 my_server = DevServerWrapper()
964 my_server.start() 1025 my_server.start()
965 try: 1026 try:
966 _RunTestsInParallel(parser, options, test_class) 1027 _RunTestsInParallel(parser, options, test_class)
967 finally: 1028 finally:
968 my_server.Stop() 1029 my_server.Stop()
969 1030
970 else: 1031 else:
971 dev_server_cache = None 1032 dev_server_cache = None
972 test_suite = _PrepareTestSuite(parser, options, test_class) 1033 test_suite = _PrepareTestSuite(parser, options, test_class)
973 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) 1034 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite)
974 if not test_result.wasSuccessful(): 1035 if not test_result.wasSuccessful():
975 Die('Test harness was not successful.') 1036 Die('Test harness was not successful.')
976 1037
977 1038
978 if __name__ == '__main__': 1039 if __name__ == '__main__':
979 main() 1040 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