Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # | 2 # |
| 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2010 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 """Create and copy update image to target host. | 7 """Create and copy update image to target host. |
| 8 | 8 |
| 9 auto-update and devserver change out from beneath us often enough | 9 auto-update and devserver change out from beneath us often enough |
| 10 that despite having to duplicate a litte code, it seems that the | 10 that despite having to duplicate a litte code, it seems that the |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 23 import sys | 23 import sys |
| 24 import tempfile | 24 import tempfile |
| 25 import time | 25 import time |
| 26 import traceback | 26 import traceback |
| 27 | 27 |
| 28 from xml.dom import minidom | 28 from xml.dom import minidom |
| 29 | 29 |
| 30 | 30 |
| 31 # This is the default filename within the image directory to load updates from | 31 # This is the default filename within the image directory to load updates from |
| 32 DEFAULT_IMAGE_NAME = 'chromiumos_image.bin' | 32 DEFAULT_IMAGE_NAME = 'chromiumos_image.bin' |
| 33 DEFAULT_IMAGE_NAME_TEST = 'chromiumos_test_image.bin' | |
| 33 | 34 |
| 34 # The filenames we provide to clients to pull updates | 35 # The filenames we provide to clients to pull updates |
| 35 UPDATE_FILENAME = 'update.gz' | 36 UPDATE_FILENAME = 'update.gz' |
| 36 STATEFUL_FILENAME = 'stateful.image.gz' | 37 STATEFUL_FILENAME = 'stateful.gz' |
|
Paul Stewart
2010/11/23 21:21:33
The old stateful.gz is dead, I believe, and remove
| |
| 38 STATEFUL_FILENAME_TGZ = 'stateful.tgz' | |
| 37 | 39 |
| 38 # How long do we wait for the server to start before launching client | 40 # How long do we wait for the server to start before launching client |
| 39 SERVER_STARTUP_WAIT = 1 | 41 SERVER_STARTUP_WAIT = 1 |
| 40 | 42 |
| 41 | 43 |
| 42 class Command(object): | 44 class Command(object): |
| 43 """Shell command ease-ups for Python.""" | 45 """Shell command ease-ups for Python.""" |
| 44 | 46 |
| 45 def __init__(self, env): | 47 def __init__(self, env): |
| 46 self.env = env | 48 self.env = env |
| 47 | 49 |
| 48 def RunPipe(self, pipeline, infile=None, outfile=None, | 50 def RunPipe(self, pipeline, infile=None, outfile=None, |
| 49 capture=False, oneline=False): | 51 capture=False, oneline=False, hide_stderr=False): |
| 50 """Perform a command pipeline, with optional input/output filenames.""" | 52 """ |
| 53 Perform a command pipeline, with optional input/output filenames. | |
| 54 | |
| 55 hide_stderr Don't allow output of stderr (default False) | |
| 56 """ | |
| 51 | 57 |
| 52 last_pipe = None | 58 last_pipe = None |
| 53 while pipeline: | 59 while pipeline: |
| 54 cmd = pipeline.pop(0) | 60 cmd = pipeline.pop(0) |
| 55 kwargs = {} | 61 kwargs = {} |
| 56 if last_pipe is not None: | 62 if last_pipe is not None: |
| 57 kwargs['stdin'] = last_pipe.stdout | 63 kwargs['stdin'] = last_pipe.stdout |
| 58 elif infile: | 64 elif infile: |
| 59 kwargs['stdin'] = open(infile, 'rb') | 65 kwargs['stdin'] = open(infile, 'rb') |
| 60 if pipeline or capture: | 66 if pipeline or capture: |
| 61 kwargs['stdout'] = subprocess.PIPE | 67 kwargs['stdout'] = subprocess.PIPE |
| 62 elif outfile: | 68 elif outfile: |
| 63 kwargs['stdout'] = open(outfile, 'wb') | 69 kwargs['stdout'] = open(outfile, 'wb') |
| 70 if hide_stderr: | |
| 71 kwargs['stderr'] = open('/dev/null', 'wb') | |
| 64 | 72 |
| 65 self.env.Info('Running: %s' % ' '.join(cmd)) | 73 self.env.Debug('Running: %s' % ' '.join(cmd)) |
| 66 last_pipe = subprocess.Popen(cmd, **kwargs) | 74 last_pipe = subprocess.Popen(cmd, **kwargs) |
| 67 | 75 |
| 68 if capture: | 76 if capture: |
| 69 ret = last_pipe.communicate()[0] | 77 ret = last_pipe.communicate()[0] |
| 70 if not ret: | 78 if not ret: |
| 71 return None | 79 return None |
| 72 elif oneline: | 80 elif oneline: |
| 73 return ret.rstrip('\r\n') | 81 return ret.rstrip('\r\n') |
| 74 else: | 82 else: |
| 75 return ret | 83 return ret |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 103 self.known_hosts = os.path.join(self.ssh_dir, 'known-hosts') | 111 self.known_hosts = os.path.join(self.ssh_dir, 'known-hosts') |
| 104 | 112 |
| 105 def Cleanup(self): | 113 def Cleanup(self): |
| 106 Command.RunPipe(self, [['rm', '-rf', self.ssh_dir]]) | 114 Command.RunPipe(self, [['rm', '-rf', self.ssh_dir]]) |
| 107 self.ssh_dir = None | 115 self.ssh_dir = None |
| 108 | 116 |
| 109 def GetArgs(self): | 117 def GetArgs(self): |
| 110 if not self.ssh_dir: | 118 if not self.ssh_dir: |
| 111 self.Setup() | 119 self.Setup() |
| 112 | 120 |
| 121 # allow up to 3 seconds to connect | |
| 113 return ['-o', 'Compression=no', | 122 return ['-o', 'Compression=no', |
| 114 '-o', 'ConnectTimeout=%d' % self.CONNECT_TIMEOUT, | 123 '-o', 'ConnectTimeout=%d' % self.CONNECT_TIMEOUT, |
| 115 '-o', 'StrictHostKeyChecking=no', | 124 '-o', 'StrictHostKeyChecking=no', |
| 116 '-o', 'UserKnownHostsFile=%s' % self.known_hosts, | 125 '-o', 'UserKnownHostsFile=%s' % self.known_hosts, |
| 117 '-i', self.identity] | 126 '-i', self.identity] |
| 118 | 127 |
| 119 def RunPipe(self, pipeline, **kwargs): | 128 def RunPipe(self, pipeline, **kwargs): |
| 120 args = ['ssh'] + self.GetArgs() | 129 args = ['ssh'] + self.GetArgs() |
| 121 if 'remote_tunnel' in kwargs: | 130 if 'remote_tunnel' in kwargs: |
| 122 ports = kwargs.pop('remote_tunnel') | 131 ports = kwargs.pop('remote_tunnel') |
| 123 args += ['-R %d:localhost:%d' % ports] | 132 args += ['-R %d:localhost:%d' % ports] |
| 124 pipeline[0] = args + ['root@%s' % self.remote] + list(pipeline[0]) | 133 pipeline[0] = args + ['root@%s' % self.remote] + list(pipeline[0]) |
| 125 return Command.RunPipe(self, pipeline, **kwargs) | 134 return Command.RunPipe(self, pipeline, **kwargs) |
| 126 | 135 |
| 127 def Reset(self): | 136 def Reset(self): |
| 128 os.unlink(self.known_hosts) | 137 os.unlink(self.known_hosts) |
| 129 | 138 |
| 130 def Copy(self, src, dest): | 139 def Copy(self, src, dest): |
| 131 return Command.RunPipe(self, [['scp'] + self.GetArgs() + | 140 return Command.RunPipe(self, [['scp'] + self.GetArgs() + |
| 132 [src, 'root@%s:%s' % | 141 [src, 'root@%s:%s' % |
| 133 (self.remote, dest)]]) | 142 (self.remote, dest)]]) |
| 134 | 143 |
| 135 | 144 |
| 136 class CrosEnv(object): | 145 class CrosEnv(object): |
| 137 """Encapsulates the ChromeOS build system environment functionality.""" | 146 """Encapsulates the ChromeOS build system environment functionality.""" |
| 138 | 147 |
| 139 REBOOT_START_WAIT = 5 | 148 REBOOT_START_WAIT = 5 |
| 140 REBOOT_WAIT_TIME = 60 | 149 REBOOT_WAIT_TIME = 60 |
| 141 | 150 |
| 142 def __init__(self, verbose=False): | 151 def __init__(self, verbose=0): |
| 143 self.cros_root = os.path.dirname(os.path.abspath(sys.argv[0])) | 152 self.cros_root = os.path.dirname(os.path.abspath(sys.argv[0])) |
| 144 parent = os.path.dirname(self.cros_root) | 153 parent = os.path.dirname(self.cros_root) |
| 145 if os.path.exists(os.path.join(parent, 'chromeos-common.sh')): | 154 if os.path.exists(os.path.join(parent, 'chromeos-common.sh')): |
| 146 self.cros_root = parent | 155 self.cros_root = parent |
| 147 self.cmd = Command(self) | 156 self.cmd = Command(self) |
| 148 self.verbose = verbose | 157 self.verbose = verbose |
| 149 | 158 |
| 159 # do we have the pv progress tool? (sudo apt-get install pv) | |
| 160 self.have_pv = True | |
| 161 try: | |
| 162 self.cmd.Output('pv', '--help') | |
| 163 except OSError: | |
| 164 self.have_pv = False | |
| 165 | |
| 150 def Error(self, msg): | 166 def Error(self, msg): |
| 151 print >> sys.stderr, 'ERROR: %s' % msg | 167 print >> sys.stderr, 'ERROR: %s' % msg |
| 152 | 168 |
| 153 def Fatal(self, msg=None): | 169 def Fatal(self, msg=None): |
| 154 if msg: | 170 if msg: |
| 155 self.Error(msg) | 171 self.Error(msg) |
| 156 sys.exit(1) | 172 sys.exit(1) |
| 157 | 173 |
| 158 def Info(self, msg): | 174 def Info(self, msg): |
| 159 if self.verbose: | 175 if self.verbose > 0: |
| 160 print 'INFO: %s' % msg | 176 print 'INFO: %s' % msg |
| 161 | 177 |
| 178 def Debug(self, msg): | |
| 179 if self.verbose > 1: | |
| 180 print 'DEBUG: %s' % msg | |
| 181 | |
| 162 def CrosUtilsPath(self, filename): | 182 def CrosUtilsPath(self, filename): |
| 163 return os.path.join(self.cros_root, filename) | 183 return os.path.join(self.cros_root, filename) |
| 164 | 184 |
| 165 def ChrootPath(self, filename): | 185 def ChrootPath(self, filename): |
| 166 return self.CrosUtilsPath(os.path.join('..', '..', 'chroot', | 186 return self.CrosUtilsPath(os.path.join('..', '..', 'chroot', |
| 167 filename.strip(os.path.sep))) | 187 filename.strip(os.path.sep))) |
| 168 | 188 |
| 169 def FileOneLine(self, filename): | 189 def FileOneLine(self, filename): |
| 170 return file(filename).read().rstrip('\r\n') | 190 return file(filename).read().rstrip('\r\n') |
| 171 | 191 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 192 | 212 |
| 193 return True | 213 return True |
| 194 | 214 |
| 195 def BuildStateful(self, src, dst): | 215 def BuildStateful(self, src, dst): |
| 196 """Create a stateful partition update image.""" | 216 """Create a stateful partition update image.""" |
| 197 | 217 |
| 198 if self.GetCached(src, dst): | 218 if self.GetCached(src, dst): |
| 199 self.Info('Using cached stateful %s' % dst) | 219 self.Info('Using cached stateful %s' % dst) |
| 200 return True | 220 return True |
| 201 | 221 |
| 222 self.Info('Building stateful') | |
| 202 cgpt = self.ChrootPath('/usr/bin/cgpt') | 223 cgpt = self.ChrootPath('/usr/bin/cgpt') |
| 203 offset = self.cmd.OutputOneLine(cgpt, 'show', '-b', '-i', '1', src) | 224 offset = self.cmd.OutputOneLine(cgpt, 'show', '-b', '-i', '1', src) |
| 204 size = self.cmd.OutputOneLine(cgpt, 'show', '-s', '-i', '1', src) | 225 size = self.cmd.OutputOneLine(cgpt, 'show', '-s', '-i', '1', src) |
| 205 if None in (size, offset): | 226 if None in (size, offset): |
| 206 self.Error('Unable to use cgpt to get image geometry') | 227 self.Error('Unable to use cgpt to get image geometry') |
| 207 return False | 228 return False |
| 208 | 229 |
| 209 return self.cmd.RunPipe([['dd', 'if=%s' % src, 'bs=512', | 230 return self.cmd.RunPipe([ |
| 210 'skip=%s' % offset, 'count=%s' % size], | 231 ['dd', 'if=%s' % src, 'bs=512', 'skip=%s' % offset, 'count=%s' % size], |
| 211 ['gzip', '-c']], outfile=dst) | 232 self.have_pv and ['pv', '-s', '%d' % (int(size) * 512)] or ['cat'], |
| 233 ['gzip', '-c']], outfile=dst) | |
| 212 | 234 |
| 213 def GetSize(self, filename): | 235 def GetSize(self, filename): |
| 214 return os.path.getsize(filename) | 236 return os.path.getsize(filename) |
| 215 | 237 |
| 216 def GetHash(self, filename): | 238 def GetHash(self, filename): |
| 217 return self.cmd.RunPipe([['openssl', 'sha1', '-binary'], | 239 return self.cmd.RunPipe([['openssl', 'sha1', '-binary'], |
| 218 ['openssl', 'base64']], | 240 ['openssl', 'base64']], |
| 219 infile=filename, | 241 infile=filename, |
| 220 capture=True, oneline=True) | 242 capture=True, oneline=True) |
| 221 | 243 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 245 if var: | 267 if var: |
| 246 ret[var] = val.strip('\t ').rstrip('\t ') | 268 ret[var] = val.strip('\t ').rstrip('\t ') |
| 247 return ret | 269 return ret |
| 248 | 270 |
| 249 def GetRemoteRelease(self): | 271 def GetRemoteRelease(self): |
| 250 lsb_release = self.ssh_cmd.Output('cat', '/etc/lsb-release') | 272 lsb_release = self.ssh_cmd.Output('cat', '/etc/lsb-release') |
| 251 if not lsb_release: | 273 if not lsb_release: |
| 252 return None | 274 return None |
| 253 return self.ParseShVars(lsb_release) | 275 return self.ParseShVars(lsb_release) |
| 254 | 276 |
| 255 def CreateServer(self, port, update_file, stateful_file): | 277 def CreateServer(self, port, update_file, stateful_file, stateful_file_tgz): |
| 256 """Start the devserver clone.""" | 278 """Start the devserver clone.""" |
| 257 | 279 |
| 258 PingUpdateResponse.Setup(self.GetHash(update_file), | 280 PingUpdateResponse.Setup(self.GetHash(update_file), |
| 259 self.GetSha256(update_file), | 281 self.GetSha256(update_file), |
| 260 self.GetSize(update_file)) | 282 self.GetSize(update_file)) |
| 261 | 283 |
| 262 UpdateHandler.SetupUrl('/update', PingUpdateResponse()) | 284 UpdateHandler.SetupUrl('/update', PingUpdateResponse()) |
| 263 UpdateHandler.SetupUrl('/%s' % UPDATE_FILENAME, | 285 UpdateHandler.SetupUrl('/%s' % UPDATE_FILENAME, |
| 264 FileUpdateResponse(update_file, | 286 FileUpdateResponse(update_file, |
| 265 verbose=self.verbose)) | 287 verbose=self.verbose, have_pv=self.have_pv)) |
| 266 UpdateHandler.SetupUrl('/%s' % STATEFUL_FILENAME, | 288 UpdateHandler.SetupUrl('/%s' % STATEFUL_FILENAME, |
| 267 FileUpdateResponse(stateful_file, | 289 FileUpdateResponse(stateful_file, |
| 268 verbose=self.verbose)) | 290 verbose=self.verbose, have_pv=self.have_pv)) |
| 291 UpdateHandler.SetupUrl('/%s' % STATEFUL_FILENAME_TGZ, | |
| 292 FileUpdateResponse(stateful_file_tgz, | |
| 293 verbose=self.verbose, have_pv=self.have_pv)) | |
|
Paul Stewart
2010/11/23 21:21:33
Please rebase these changes on what's currently in
| |
| 269 | 294 |
| 270 self.http_server = BaseHTTPServer.HTTPServer(('', port), UpdateHandler) | 295 self.http_server = BaseHTTPServer.HTTPServer(('', port), UpdateHandler) |
| 271 | 296 |
| 272 def StartServer(self): | 297 def StartServer(self): |
| 273 self.Info('Starting http server') | 298 self.Info('Starting http server') |
| 274 self.http_server.serve_forever() | 299 self.http_server.serve_forever() |
| 275 | 300 |
| 276 def GetUpdateStatus(self): | 301 def GetUpdateStatus(self): |
| 277 status = self.ssh_cmd.Output('/usr/bin/update_engine_client', '--status') | 302 status = self.ssh_cmd.Output('/usr/bin/update_engine_client', '--status') |
| 278 if not status: | 303 if not status: |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 297 self.Info('Client has not yet restarted (try %d). Waiting...' % attempt) | 322 self.Info('Client has not yet restarted (try %d). Waiting...' % attempt) |
| 298 wait_time = SSHCommand.CONNECT_TIMEOUT - (time.time() - start) | 323 wait_time = SSHCommand.CONNECT_TIMEOUT - (time.time() - start) |
| 299 if wait_time > 0: | 324 if wait_time > 0: |
| 300 time.sleep(wait_time) | 325 time.sleep(wait_time) |
| 301 | 326 |
| 302 return False | 327 return False |
| 303 | 328 |
| 304 def StartClient(self, port): | 329 def StartClient(self, port): |
| 305 """Ask the client machine to update from our server.""" | 330 """Ask the client machine to update from our server.""" |
| 306 | 331 |
| 332 self.Info("Starting client...") | |
| 307 status = self.GetUpdateStatus() | 333 status = self.GetUpdateStatus() |
| 308 if status != 'UPDATE_STATUS_IDLE': | 334 if status != 'UPDATE_STATUS_IDLE': |
| 309 self.Error('Client update status is not IDLE: %s' % status) | 335 self.Error('Client update status is not IDLE: %s' % status) |
| 310 return False | 336 return False |
| 311 | 337 |
| 312 url_base = 'http://localhost:%d' % port | 338 url_base = 'http://localhost:%d' % port |
| 313 update_url = '%s/update' % url_base | 339 update_url = '%s/update' % url_base |
| 314 fd, update_log = tempfile.mkstemp(prefix='image-to-target-') | 340 fd, update_log = tempfile.mkstemp(prefix='image-to-target-') |
| 315 self.Info('Starting update on client. Client output stored to %s' % | 341 self.Info('Starting update on client. Client output stored to %s' % |
| 316 update_log) | 342 update_log) |
| 343 | |
| 344 # this will make the client read the files we have set up | |
| 317 self.ssh_cmd.Run('/usr/bin/update_engine_client', '--update', | 345 self.ssh_cmd.Run('/usr/bin/update_engine_client', '--update', |
| 318 '--omaha_url', update_url, remote_tunnel=(port, port), | 346 '--omaha_url', update_url, remote_tunnel=(port, port), |
| 319 outfile=update_log) | 347 outfile=update_log) |
| 320 | 348 |
| 321 if self.GetUpdateStatus() != 'UPDATE_STATUS_UPDATED_NEED_REBOOT': | 349 if self.GetUpdateStatus() != 'UPDATE_STATUS_UPDATED_NEED_REBOOT': |
| 322 self.Error('Client update failed') | 350 self.Error('Client update failed') |
| 323 return False | 351 return False |
| 324 | 352 |
| 353 self.Info('Update complete - running update script on client') | |
| 325 self.ssh_cmd.Copy(self.CrosUtilsPath('../platform/dev/stateful_update'), | 354 self.ssh_cmd.Copy(self.CrosUtilsPath('../platform/dev/stateful_update'), |
| 326 '/tmp') | 355 '/tmp') |
| 327 if not self.ssh_cmd.Run('/tmp/stateful_update', url_base, | 356 if not self.ssh_cmd.Run('/tmp/stateful_update', url_base, |
| 328 remote_tunnel=(port, port)): | 357 remote_tunnel=(port, port)): |
| 329 self.Error('Client stateful update failed') | 358 self.Error('Client stateful update failed') |
| 330 return False | 359 return False |
| 331 | 360 |
| 332 self.Info('Rebooting client') | 361 self.Info('Rebooting client') |
| 333 if not self.ClientReboot(): | 362 if not self.ClientReboot(): |
| 334 self.Error('Client may not have successfully rebooted...') | 363 self.Error('Client may not have successfully rebooted...') |
| 335 return False | 364 return False |
| 336 | 365 |
| 337 print 'Client update completed successfully!' | 366 self.Info('Client update completed successfully!') |
| 338 return True | 367 return True |
| 339 | 368 |
| 340 | 369 |
| 341 class UpdateResponse(object): | 370 class UpdateResponse(object): |
| 342 """Default response is the 404 error response.""" | 371 """Default response is the 404 error response.""" |
| 343 | 372 |
| 344 def Reply(self, handler, send_content=True, post_data=None): | 373 def Reply(self, handler, send_content=True, post_data=None): |
| 345 handler.send_Error(404, 'File not found') | 374 handler.send_error(404, 'File not found') |
| 346 return None | 375 return None |
| 347 | 376 |
| 348 | 377 |
| 349 class FileUpdateResponse(UpdateResponse): | 378 class FileUpdateResponse(UpdateResponse): |
| 350 """Respond by sending the contents of a file.""" | 379 """Respond by sending the contents of a file.""" |
| 351 | 380 |
| 352 def __init__(self, filename, content_type='application/octet-stream', | 381 def __init__(self, filename, content_type='application/octet-stream', |
| 353 verbose=False, blocksize=16*1024): | 382 verbose=False, blocksize=16*1024, have_pv=False): |
| 354 self.filename = filename | 383 self.filename = filename |
| 355 self.content_type = content_type | 384 self.content_type = content_type |
| 356 self.verbose = verbose | 385 self.verbose = verbose |
| 357 self.blocksize = blocksize | 386 self.blocksize = blocksize |
| 387 self.have_pv = have_pv | |
| 358 | 388 |
| 359 def Reply(self, handler, send_content=True, post_data=None): | 389 def Reply(self, handler, send_content=True, post_data=None): |
| 360 """Return file contents to the client. Optionally display progress.""" | 390 """Return file contents to the client. Optionally display progress.""" |
| 361 | 391 |
| 362 try: | 392 try: |
| 363 f = open(self.filename, 'rb') | 393 f = open(self.filename, 'rb') |
| 364 except IOError: | 394 except IOError: |
| 365 return UpdateResponse.Reply(self, handler) | 395 return UpdateResponse.Reply(self, handler) |
| 366 | 396 |
| 367 handler.send_response(200) | 397 handler.send_response(200) |
| 368 handler.send_header('Content-type', self.content_type) | 398 handler.send_header('Content-type', self.content_type) |
| 369 filestat = os.fstat(f.fileno()) | 399 filestat = os.fstat(f.fileno()) |
| 370 filesize = filestat[6] | 400 filesize = filestat[6] |
| 371 handler.send_header('Content-Length', str(filesize)) | 401 handler.send_header('Content-Length', str(filesize)) |
| 372 handler.send_header('Last-Modified', | 402 handler.send_header('Last-Modified', |
| 373 handler.date_time_string(filestat.st_mtime)) | 403 handler.date_time_string(filestat.st_mtime)) |
| 374 handler.end_headers() | 404 handler.end_headers() |
| 375 | 405 |
| 376 if not send_content: | 406 if send_content: |
| 377 return | |
| 378 | |
| 379 if filesize <= self.blocksize: | |
| 380 handler.wfile.write(f.read()) | |
| 381 else: | |
| 382 sent_size = 0 | 407 sent_size = 0 |
| 383 sent_percentage = None | 408 sent_percentage = None |
| 409 | |
| 410 #FIXME: this should use pv also | |
|
Paul Stewart
2010/11/23 21:21:33
Use # TODO(sgj): ...
| |
| 384 while True: | 411 while True: |
| 385 buf = f.read(self.blocksize) | 412 buf = f.read(self.blocksize) |
| 386 if not buf: | 413 if not buf: |
| 387 break | 414 break |
| 388 handler.wfile.write(buf) | 415 handler.wfile.write(buf) |
| 389 if self.verbose: | 416 if self.verbose: |
| 390 sent_size += len(buf) | 417 sent_size += len(buf) |
| 391 percentage = int(100 * sent_size / filesize) | 418 percentage = int(100 * sent_size / filesize) |
| 392 if sent_percentage != percentage: | 419 if sent_percentage != percentage: |
| 393 sent_percentage = percentage | 420 sent_percentage = percentage |
| (...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 549 usage = 'usage: %prog [options]' | 576 usage = 'usage: %prog [options]' |
| 550 parser = optparse.OptionParser(usage=usage) | 577 parser = optparse.OptionParser(usage=usage) |
| 551 parser.add_option('--board', dest='board', default=None, | 578 parser.add_option('--board', dest='board', default=None, |
| 552 help='Board platform type') | 579 help='Board platform type') |
| 553 parser.add_option('--force-mismatch', dest='force_mismatch', default=False, | 580 parser.add_option('--force-mismatch', dest='force_mismatch', default=False, |
| 554 action='store_true', | 581 action='store_true', |
| 555 help='Upgrade even if client arch does not match') | 582 help='Upgrade even if client arch does not match') |
| 556 parser.add_option('--from', dest='src', default=None, | 583 parser.add_option('--from', dest='src', default=None, |
| 557 help='Source image to install') | 584 help='Source image to install') |
| 558 parser.add_option('--image-name', dest='image_name', | 585 parser.add_option('--image-name', dest='image_name', |
| 559 default=DEFAULT_IMAGE_NAME, | |
| 560 help='Filename within image directory to load') | 586 help='Filename within image directory to load') |
| 561 parser.add_option('--port', dest='port', default=8081, type='int', | 587 parser.add_option('--port', dest='port', default=8081, type='int', |
| 562 help='TCP port to serve from and tunnel through') | 588 help='TCP port to serve from and tunnel through') |
| 563 parser.add_option('--remote', dest='remote', default=None, | 589 parser.add_option('--remote', dest='remote', default=None, |
| 564 help='Remote device-under-test IP address') | 590 help='Remote device-under-test IP address') |
| 565 parser.add_option('--server-only', dest='server_only', default=False, | 591 parser.add_option('--server-only', dest='server_only', default=False, |
| 566 action='store_true', help='Do not start client') | 592 action='store_true', help='Do not start client') |
| 567 parser.add_option('--verbose', dest='verbose', default=False, | 593 parser.add_option('--verbose', dest='verbose', default=False, |
| 594 action='store_true', help='Display progress') | |
| 595 parser.add_option('--debug', dest='debug', default=False, | |
| 568 action='store_true', help='Display running commands') | 596 action='store_true', help='Display running commands') |
| 597 parser.add_option('--test', dest='test', default=False, | |
| 598 action='store_true', help='Select test image') | |
| 569 | 599 |
| 570 (options, args) = parser.parse_args(argv) | 600 (options, args) = parser.parse_args(argv) |
| 571 | 601 |
| 572 cros_env = CrosEnv(verbose=options.verbose) | 602 # we can build the test image if it doesn't exist, so remember if we want to |
| 603 build_test_image = False | |
| 604 | |
| 605 verbosity = 0 | |
| 606 if options.verbose: | |
| 607 verbosity = 1 | |
| 608 if options.debug: | |
| 609 verbosity = 2 | |
| 610 cros_env = CrosEnv(verbose=verbosity) | |
| 573 | 611 |
| 574 if not options.board: | 612 if not options.board: |
| 575 options.board = cros_env.GetDefaultBoard() | 613 options.board = cros_env.GetDefaultBoard() |
| 576 | 614 |
| 577 if not options.src: | 615 if not options.src: |
| 578 options.src = cros_env.GetLatestImage(options.board) | 616 options.src = cros_env.GetLatestImage(options.board) |
| 579 if options.src is None: | 617 if options.src is None: |
| 580 parser.error('No --from argument given and no default image found') | 618 parser.error('No --from argument given and no default image found') |
| 581 | 619 |
| 582 cros_env.Info('Performing update from %s' % options.src) | 620 cros_env.Info('Performing update from %s' % options.src) |
| 583 | 621 |
| 584 if not os.path.exists(options.src): | 622 if not os.path.exists(options.src): |
| 585 parser.error('Path %s does not exist' % options.src) | 623 parser.error('Path %s does not exist' % options.src) |
| 586 | 624 |
| 625 if not options.image_name: | |
| 626 # auto-select the correct image | |
| 627 if options.test: | |
| 628 options.image_name = DEFAULT_IMAGE_NAME_TEST | |
| 629 | |
| 630 # we will build the test image if not found | |
| 631 build_test_image = True | |
| 632 else: | |
| 633 options.image_name = DEFAULT_IMAGE_NAME | |
| 634 | |
| 587 if os.path.isdir(options.src): | 635 if os.path.isdir(options.src): |
| 588 image_directory = options.src | 636 image_directory = options.src |
| 589 image_file = os.path.join(options.src, options.image_name) | 637 image_file = os.path.join(options.src, options.image_name) |
| 590 | 638 |
| 591 if not os.path.exists(image_file): | 639 if not os.path.exists(image_file): |
| 640 if build_test_image: | |
| 641 # we want a test image but it doesn't exist | |
| 642 # try to build it if we can | |
| 643 cros_env.Info('Creating test image') | |
| 644 test_output = cros_env.cmd.Output( | |
| 645 cros_env.CrosUtilsPath('enter_chroot.sh'), | |
| 646 '--', './mod_image_for_test.sh', | |
| 647 '--board=%s' % options.board, '-y') | |
| 648 if not os.path.exists(image_file): | |
| 649 print test_output | |
| 650 cros_env.Fatal('Failed to create test image - please run ' | |
| 651 './mod_image_for_test.sh manually inside the chroot') | |
| 592 parser.error('Image file %s does not exist' % image_file) | 652 parser.error('Image file %s does not exist' % image_file) |
| 593 else: | 653 else: |
| 594 image_file = options.src | 654 image_file = options.src |
| 595 image_directory = os.path.dirname(options.src) | 655 image_directory = os.path.dirname(options.src) |
| 596 | 656 |
| 657 update_file = os.path.join(image_directory, UPDATE_FILENAME) | |
| 658 stateful_file = os.path.join(image_directory, STATEFUL_FILENAME) | |
| 659 stateful_file_tgz = cros_env.CrosUtilsPath( | |
| 660 '../platform/dev/static/stateful.tgz') | |
| 661 cros_env.Debug("Image file %s" % image_file) | |
| 662 cros_env.Debug("Update file %s" % update_file) | |
| 663 cros_env.Debug("Stateful file %s" % stateful_file) | |
| 664 cros_env.Debug("Stateful file tgz %s" % stateful_file_tgz) | |
| 665 | |
| 597 if options.remote: | 666 if options.remote: |
| 667 cros_env.Info('Contacting client %s' % options.remote) | |
| 598 cros_env.SetRemote(options.remote) | 668 cros_env.SetRemote(options.remote) |
| 599 rel = cros_env.GetRemoteRelease() | 669 rel = cros_env.GetRemoteRelease() |
| 600 if not rel: | 670 if not rel: |
| 601 cros_env.Fatal('Could not retrieve remote lsb-release') | 671 cros_env.Fatal('Could not retrieve remote lsb-release') |
| 602 board = rel.get('CHROMEOS_RELEASE_BOARD', '(None)') | 672 board = rel.get('CHROMEOS_RELEASE_BOARD', '(None)') |
| 603 if board != options.board and not options.force_mismatch: | 673 if board != options.board and not options.force_mismatch: |
| 604 cros_env.Error('Board %s does not match expected %s' % | 674 cros_env.Error('Board %s does not match expected %s' % |
| 605 (board, options.board)) | 675 (board, options.board)) |
| 606 cros_env.Error('(Use --force-mismatch option to override this)') | 676 cros_env.Error('(Use --force-mismatch option to override this)') |
| 607 cros_env.Fatal() | 677 cros_env.Fatal() |
| 608 | 678 |
| 609 elif not options.server_only: | 679 elif not options.server_only: |
| 610 parser.error('Either --server-only must be specified or ' | 680 parser.error('Either --server-only must be specified or ' |
| 611 '--remote=<client> needs to be given') | 681 '--remote=<client> needs to be given') |
| 612 | 682 |
| 613 update_file = os.path.join(image_directory, UPDATE_FILENAME) | |
| 614 stateful_file = os.path.join(image_directory, STATEFUL_FILENAME) | |
| 615 | |
| 616 if (not cros_env.GenerateUpdatePayload(image_file, update_file) or | 683 if (not cros_env.GenerateUpdatePayload(image_file, update_file) or |
| 617 not cros_env.BuildStateful(image_file, stateful_file)): | 684 not cros_env.BuildStateful(image_file, stateful_file)): |
| 618 cros_env.Fatal() | 685 cros_env.Fatal() |
| 619 | 686 |
| 620 cros_env.CreateServer(options.port, update_file, stateful_file) | 687 cros_env.CreateServer(options.port, update_file, stateful_file, |
| 688 stateful_file_tgz) | |
| 621 | 689 |
| 622 exit_status = 1 | 690 exit_status = 1 |
| 623 if options.server_only: | 691 if options.server_only: |
| 624 child = None | 692 child = None |
| 625 else: | 693 else: |
| 626 # Start an "image-to-live" instance that will pull bits from the server | 694 # Start an "image-to-live" instance that will pull bits from the server |
| 627 child = os.fork() | 695 child = os.fork() |
| 628 if child: | 696 if child: |
| 629 signal.signal(signal.SIGCHLD, ChildFinished(child).SigHandler) | 697 signal.signal(signal.SIGCHLD, ChildFinished(child).SigHandler) |
| 630 else: | 698 else: |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 657 | 725 |
| 658 if child: | 726 if child: |
| 659 os.kill(child, 15) | 727 os.kill(child, 15) |
| 660 | 728 |
| 661 cros_env.Info('Server exiting with status %d' % exit_status) | 729 cros_env.Info('Server exiting with status %d' % exit_status) |
| 662 sys.exit(exit_status) | 730 sys.exit(exit_status) |
| 663 | 731 |
| 664 | 732 |
| 665 if __name__ == '__main__': | 733 if __name__ == '__main__': |
| 666 main(sys.argv) | 734 main(sys.argv) |
| OLD | NEW |