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 |
11 right thing to do here is to start over and do something that is | 11 right thing to do here is to start over and do something that is |
12 simple enough and easy enough to understand so that when more | 12 simple enough and easy enough to understand so that when more |
13 stuff breaks, at least we can solve them faster. | 13 stuff breaks, at least we can solve them faster. |
14 """ | 14 """ |
15 | 15 |
16 import BaseHTTPServer | 16 import BaseHTTPServer |
17 import cgi | 17 import cgi |
18 import errno | 18 import errno |
19 import optparse | 19 import optparse |
20 import os | 20 import os |
21 import signal | 21 import signal |
22 import subprocess | 22 import subprocess |
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' |
37 | 38 |
38 # How long do we wait for the server to start before launching client | 39 # How long do we wait for the server to start before launching client |
39 SERVER_STARTUP_WAIT = 1 | 40 SERVER_STARTUP_WAIT = 1 |
40 | 41 |
41 | 42 |
42 class Command(object): | 43 class Command(object): |
43 """Shell command ease-ups for Python.""" | 44 """Shell command ease-ups for Python.""" |
44 | 45 |
45 def __init__(self, env): | 46 def __init__(self, env): |
46 self.env = env | 47 self.env = env |
47 | 48 |
48 def RunPipe(self, pipeline, infile=None, outfile=None, | 49 def RunPipe(self, pipeline, infile=None, outfile=None, |
49 capture=False, oneline=False): | 50 capture=False, oneline=False, hide_stderr=False): |
50 """Perform a command pipeline, with optional input/output filenames.""" | 51 """ |
52 Perform a command pipeline, with optional input/output filenames. | |
53 | |
54 hide_stderr Don't allow output of stderr (default False) | |
55 """ | |
51 | 56 |
52 last_pipe = None | 57 last_pipe = None |
53 while pipeline: | 58 while pipeline: |
54 cmd = pipeline.pop(0) | 59 cmd = pipeline.pop(0) |
55 kwargs = {} | 60 kwargs = {} |
56 if last_pipe is not None: | 61 if last_pipe is not None: |
57 kwargs['stdin'] = last_pipe.stdout | 62 kwargs['stdin'] = last_pipe.stdout |
58 elif infile: | 63 elif infile: |
59 kwargs['stdin'] = open(infile, 'rb') | 64 kwargs['stdin'] = open(infile, 'rb') |
60 if pipeline or capture: | 65 if pipeline or capture: |
61 kwargs['stdout'] = subprocess.PIPE | 66 kwargs['stdout'] = subprocess.PIPE |
62 elif outfile: | 67 elif outfile: |
63 kwargs['stdout'] = open(outfile, 'wb') | 68 kwargs['stdout'] = open(outfile, 'wb') |
69 if hide_stderr: | |
70 kwargs['stderr'] = open('/dev/null', 'wb') | |
64 | 71 |
65 self.env.Info('Running: %s' % ' '.join(cmd)) | 72 self.env.Debug('Running: %s' % ' '.join(cmd)) |
66 last_pipe = subprocess.Popen(cmd, **kwargs) | 73 last_pipe = subprocess.Popen(cmd, **kwargs) |
67 | 74 |
68 if capture: | 75 if capture: |
69 ret = last_pipe.communicate()[0] | 76 ret = last_pipe.communicate()[0] |
70 if not ret: | 77 if not ret: |
71 return None | 78 return None |
72 elif oneline: | 79 elif oneline: |
73 return ret.rstrip('\r\n') | 80 return ret.rstrip('\r\n') |
74 else: | 81 else: |
75 return ret | 82 return ret |
(...skipping 27 matching lines...) Expand all Loading... | |
103 self.known_hosts = os.path.join(self.ssh_dir, 'known-hosts') | 110 self.known_hosts = os.path.join(self.ssh_dir, 'known-hosts') |
104 | 111 |
105 def Cleanup(self): | 112 def Cleanup(self): |
106 Command.RunPipe(self, [['rm', '-rf', self.ssh_dir]]) | 113 Command.RunPipe(self, [['rm', '-rf', self.ssh_dir]]) |
107 self.ssh_dir = None | 114 self.ssh_dir = None |
108 | 115 |
109 def GetArgs(self): | 116 def GetArgs(self): |
110 if not self.ssh_dir: | 117 if not self.ssh_dir: |
111 self.Setup() | 118 self.Setup() |
112 | 119 |
120 # allow up to 3 seconds to connect | |
113 return ['-o', 'Compression=no', | 121 return ['-o', 'Compression=no', |
114 '-o', 'ConnectTimeout=%d' % self.CONNECT_TIMEOUT, | 122 '-o', 'ConnectTimeout=%d' % self.CONNECT_TIMEOUT, |
115 '-o', 'StrictHostKeyChecking=no', | 123 '-o', 'StrictHostKeyChecking=no', |
124 '-o', 'ConnectTimeout=3', | |
Paul Stewart
2010/11/17 21:37:36
Look 2 lines above this one. I even used a consta
sjg
2010/11/23 18:49:14
Sorry! It was hanging at some point and I was look
| |
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 = (os.system('pv --help >/dev/null 2>&1') == 0) | |
Paul Stewart
2010/11/17 21:37:36
I'd much prefer if you used self.cmd.Run() instead
| |
161 | |
150 def Error(self, msg): | 162 def Error(self, msg): |
151 print >> sys.stderr, 'ERROR: %s' % msg | 163 print >> sys.stderr, 'ERROR: %s' % msg |
152 | 164 |
153 def Fatal(self, msg=None): | 165 def Fatal(self, msg=None): |
154 if msg: | 166 if msg: |
155 self.Error(msg) | 167 self.Error(msg) |
156 sys.exit(1) | 168 sys.exit(1) |
157 | 169 |
158 def Info(self, msg): | 170 def Info(self, msg): |
159 if self.verbose: | 171 if self.verbose > 0: |
160 print 'INFO: %s' % msg | 172 print 'INFO: %s' % msg |
161 | 173 |
174 def Debug(self, msg): | |
175 if self.verbose > 1: | |
176 print 'DEBUG: %s' % msg | |
177 | |
162 def CrosUtilsPath(self, filename): | 178 def CrosUtilsPath(self, filename): |
163 return os.path.join(self.cros_root, filename) | 179 return os.path.join(self.cros_root, filename) |
164 | 180 |
165 def ChrootPath(self, filename): | 181 def ChrootPath(self, filename): |
166 return self.CrosUtilsPath(os.path.join('..', '..', 'chroot', | 182 return self.CrosUtilsPath(os.path.join('..', '..', 'chroot', |
167 filename.strip(os.path.sep))) | 183 filename.strip(os.path.sep))) |
168 | 184 |
169 def FileOneLine(self, filename): | 185 def FileOneLine(self, filename): |
170 return file(filename).read().rstrip('\r\n') | 186 return file(filename).read().rstrip('\r\n') |
171 | 187 |
(...skipping 20 matching lines...) Expand all Loading... | |
192 | 208 |
193 return True | 209 return True |
194 | 210 |
195 def BuildStateful(self, src, dst): | 211 def BuildStateful(self, src, dst): |
196 """Create a stateful partition update image.""" | 212 """Create a stateful partition update image.""" |
197 | 213 |
198 if self.GetCached(src, dst): | 214 if self.GetCached(src, dst): |
199 self.Info('Using cached stateful %s' % dst) | 215 self.Info('Using cached stateful %s' % dst) |
200 return True | 216 return True |
201 | 217 |
218 self.Info('Building stateful') | |
202 cgpt = self.ChrootPath('/usr/bin/cgpt') | 219 cgpt = self.ChrootPath('/usr/bin/cgpt') |
203 offset = self.cmd.OutputOneLine(cgpt, 'show', '-b', '-i', '1', src) | 220 offset = self.cmd.OutputOneLine(cgpt, 'show', '-b', '-i', '1', src) |
204 size = self.cmd.OutputOneLine(cgpt, 'show', '-s', '-i', '1', src) | 221 size = self.cmd.OutputOneLine(cgpt, 'show', '-s', '-i', '1', src) |
205 if None in (size, offset): | 222 if None in (size, offset): |
206 self.Error('Unable to use cgpt to get image geometry') | 223 self.Error('Unable to use cgpt to get image geometry') |
207 return False | 224 return False |
208 | 225 |
209 return self.cmd.RunPipe([['dd', 'if=%s' % src, 'bs=512', | 226 return self.cmd.RunPipe([ |
210 'skip=%s' % offset, 'count=%s' % size], | 227 ['dd', 'if=%s' % src, 'bs=512', 'skip=%s' % offset, 'count=%s' % size], |
211 ['gzip', '-c']], outfile=dst) | 228 self.have_pv and ['pv', '-s', '%d' % (int (size) * 512)] or ['cat'], |
229 ['gzip', '-c']], outfile=dst, hide_stderr=True) | |
212 | 230 |
213 def GetSize(self, filename): | 231 def GetSize(self, filename): |
214 return os.path.getsize(filename) | 232 return os.path.getsize(filename) |
215 | 233 |
216 def GetHash(self, filename): | 234 def GetHash(self, filename): |
217 return self.cmd.RunPipe([['openssl', 'sha1', '-binary'], | 235 return self.cmd.RunPipe([['openssl', 'sha1', '-binary'], |
218 ['openssl', 'base64']], | 236 ['openssl', 'base64']], |
219 infile=filename, | 237 infile=filename, |
220 capture=True, oneline=True) | 238 capture=True, oneline=True) |
221 | 239 |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
255 def CreateServer(self, port, update_file, stateful_file): | 273 def CreateServer(self, port, update_file, stateful_file): |
256 """Start the devserver clone.""" | 274 """Start the devserver clone.""" |
257 | 275 |
258 PingUpdateResponse.Setup(self.GetHash(update_file), | 276 PingUpdateResponse.Setup(self.GetHash(update_file), |
259 self.GetSha256(update_file), | 277 self.GetSha256(update_file), |
260 self.GetSize(update_file)) | 278 self.GetSize(update_file)) |
261 | 279 |
262 UpdateHandler.SetupUrl('/update', PingUpdateResponse()) | 280 UpdateHandler.SetupUrl('/update', PingUpdateResponse()) |
263 UpdateHandler.SetupUrl('/%s' % UPDATE_FILENAME, | 281 UpdateHandler.SetupUrl('/%s' % UPDATE_FILENAME, |
264 FileUpdateResponse(update_file, | 282 FileUpdateResponse(update_file, |
265 verbose=self.verbose)) | 283 verbose=self.verbose, have_pv=self .have_pv)) |
adlr
2010/11/17 20:43:25
80 cols here, and a couple lines down as well
| |
266 UpdateHandler.SetupUrl('/%s' % STATEFUL_FILENAME, | 284 UpdateHandler.SetupUrl('/%s' % STATEFUL_FILENAME, |
267 FileUpdateResponse(stateful_file, | 285 FileUpdateResponse(stateful_file, |
268 verbose=self.verbose)) | 286 verbose=self.verbose, have_pv=self .have_pv)) |
269 | 287 |
270 self.http_server = BaseHTTPServer.HTTPServer(('', port), UpdateHandler) | 288 self.http_server = BaseHTTPServer.HTTPServer(('', port), UpdateHandler) |
271 | 289 |
272 def StartServer(self): | 290 def StartServer(self): |
273 self.Info('Starting http server') | 291 self.Info('Starting http server') |
274 self.http_server.serve_forever() | 292 self.http_server.serve_forever() |
275 | 293 |
276 def GetUpdateStatus(self): | 294 def GetUpdateStatus(self): |
277 status = self.ssh_cmd.Output('/usr/bin/update_engine_client', '--status') | 295 status = self.ssh_cmd.Output('/usr/bin/update_engine_client', '--status') |
278 if not status: | 296 if not status: |
(...skipping 18 matching lines...) Expand all Loading... | |
297 self.Info('Client has not yet restarted (try %d). Waiting...' % attempt) | 315 self.Info('Client has not yet restarted (try %d). Waiting...' % attempt) |
298 wait_time = SSHCommand.CONNECT_TIMEOUT - (time.time() - start) | 316 wait_time = SSHCommand.CONNECT_TIMEOUT - (time.time() - start) |
299 if wait_time > 0: | 317 if wait_time > 0: |
300 time.sleep(wait_time) | 318 time.sleep(wait_time) |
301 | 319 |
302 return False | 320 return False |
303 | 321 |
304 def StartClient(self, port): | 322 def StartClient(self, port): |
305 """Ask the client machine to update from our server.""" | 323 """Ask the client machine to update from our server.""" |
306 | 324 |
325 self.Info ("Starting client...") | |
adlr
2010/11/17 20:43:25
i think style says no spaces between method name a
| |
307 status = self.GetUpdateStatus() | 326 status = self.GetUpdateStatus() |
308 if status != 'UPDATE_STATUS_IDLE': | 327 if status != 'UPDATE_STATUS_IDLE': |
309 self.Error('Client update status is not IDLE: %s' % status) | 328 self.Error('Client update status is not IDLE: %s' % status) |
310 return False | 329 return False |
311 | 330 |
312 url_base = 'http://localhost:%d' % port | 331 url_base = 'http://localhost:%d' % port |
313 update_url = '%s/update' % url_base | 332 update_url = '%s/update' % url_base |
314 fd, update_log = tempfile.mkstemp(prefix='image-to-target-') | 333 fd, update_log = tempfile.mkstemp(prefix='image-to-target-') |
315 self.Info('Starting update on client. Client output stored to %s' % | 334 self.Info('Starting update on client. Client output stored to %s' % |
316 update_log) | 335 update_log) |
317 self.ssh_cmd.Run('/usr/bin/update_engine_client', '--update', | 336 self.ssh_cmd.Run('/usr/bin/update_engine_client', '--update', |
318 '--omaha_url', update_url, remote_tunnel=(port, port), | 337 '--omaha_url', update_url, remote_tunnel=(port, port), |
319 outfile=update_log) | 338 outfile=update_log) |
320 | 339 |
321 if self.GetUpdateStatus() != 'UPDATE_STATUS_UPDATED_NEED_REBOOT': | 340 if self.GetUpdateStatus() != 'UPDATE_STATUS_UPDATED_NEED_REBOOT': |
322 self.Error('Client update failed') | 341 self.Error('Client update failed') |
323 return False | 342 return False |
324 | 343 |
344 self.Info('Update complete - running update script on client') | |
325 self.ssh_cmd.Copy(self.CrosUtilsPath('../platform/dev/stateful_update'), | 345 self.ssh_cmd.Copy(self.CrosUtilsPath('../platform/dev/stateful_update'), |
326 '/tmp') | 346 '/tmp') |
327 if not self.ssh_cmd.Run('/tmp/stateful_update', url_base, | 347 if not self.ssh_cmd.Run('/tmp/stateful_update', url_base, |
328 remote_tunnel=(port, port)): | 348 remote_tunnel=(port, port)): |
329 self.Error('Client stateful update failed') | 349 self.Error('Client stateful update failed') |
330 return False | 350 return False |
331 | 351 |
332 self.Info('Rebooting client') | 352 self.Info('Rebooting client') |
333 if not self.ClientReboot(): | 353 if not self.ClientReboot(): |
334 self.Error('Client may not have successfully rebooted...') | 354 self.Error('Client may not have successfully rebooted...') |
335 return False | 355 return False |
336 | 356 |
337 print 'Client update completed successfully!' | 357 self.Info ('Client update completed successfully!') |
adlr
2010/11/17 20:43:25
remove space before (
| |
338 return True | 358 return True |
339 | 359 |
340 | 360 |
341 class UpdateResponse(object): | 361 class UpdateResponse(object): |
342 """Default response is the 404 error response.""" | 362 """Default response is the 404 error response.""" |
343 | 363 |
344 def Reply(self, handler, send_content=True, post_data=None): | 364 def Reply(self, handler, send_content=True, post_data=None): |
345 handler.send_Error(404, 'File not found') | 365 handler.send_error(404, 'File not found') |
346 return None | 366 return None |
347 | 367 |
348 | 368 |
349 class FileUpdateResponse(UpdateResponse): | 369 class FileUpdateResponse(UpdateResponse): |
350 """Respond by sending the contents of a file.""" | 370 """Respond by sending the contents of a file.""" |
351 | 371 |
352 def __init__(self, filename, content_type='application/octet-stream', | 372 def __init__(self, filename, content_type='application/octet-stream', |
353 verbose=False, blocksize=16*1024): | 373 verbose=False, blocksize=16*1024, have_pv=False): |
354 self.filename = filename | 374 self.filename = filename |
355 self.content_type = content_type | 375 self.content_type = content_type |
356 self.verbose = verbose | 376 self.verbose = verbose |
357 self.blocksize = blocksize | 377 self.blocksize = blocksize |
378 self.have_pv = have_pv | |
358 | 379 |
359 def Reply(self, handler, send_content=True, post_data=None): | 380 def Reply(self, handler, send_content=True, post_data=None): |
360 """Return file contents to the client. Optionally display progress.""" | 381 """Return file contents to the client. Optionally display progress.""" |
361 | 382 |
362 try: | 383 try: |
363 f = open(self.filename, 'rb') | 384 f = open(self.filename, 'rb') |
364 except IOError: | 385 except IOError: |
365 return UpdateResponse.Reply(self, handler) | 386 return UpdateResponse.Reply(self, handler) |
366 | 387 |
367 handler.send_response(200) | 388 handler.send_response(200) |
368 handler.send_header('Content-type', self.content_type) | 389 handler.send_header('Content-type', self.content_type) |
369 filestat = os.fstat(f.fileno()) | 390 filestat = os.fstat(f.fileno()) |
370 filesize = filestat[6] | 391 filesize = filestat[6] |
371 handler.send_header('Content-Length', str(filesize)) | 392 handler.send_header('Content-Length', str(filesize)) |
372 handler.send_header('Last-Modified', | 393 handler.send_header('Last-Modified', |
373 handler.date_time_string(filestat.st_mtime)) | 394 handler.date_time_string(filestat.st_mtime)) |
374 handler.end_headers() | 395 handler.end_headers() |
375 | 396 |
376 if not send_content: | 397 if send_content: |
377 return | 398 try: |
399 fp = False | |
400 sent_size = 0 | |
401 sent_percentage = None | |
378 | 402 |
379 if filesize <= self.blocksize: | 403 # sadly pv gets caught up in the server, not sure why |
380 handler.wfile.write(f.read()) | 404 if False: # self.verbose and self.have_pv: |
381 else: | 405 fp = subprocess.Popen('pv -s %d >/dev/null' % filesize, |
382 sent_size = 0 | 406 shell=True, stdin=subprocess.PIPE, close_fds=True).stdin |
383 sent_percentage = None | 407 while True: |
384 while True: | 408 buf = f.read(self.blocksize) |
385 buf = f.read(self.blocksize) | 409 if not buf: |
386 if not buf: | 410 break |
387 break | 411 handler.wfile.write(buf) |
388 handler.wfile.write(buf) | 412 if fp: |
389 if self.verbose: | 413 fp.write(buf) |
390 sent_size += len(buf) | 414 if self.verbose: |
391 percentage = int(100 * sent_size / filesize) | 415 sent_size += len(buf) |
392 if sent_percentage != percentage: | 416 percentage = int(100 * sent_size / filesize) |
393 sent_percentage = percentage | 417 if sent_percentage != percentage: |
394 print '\rSent %d%%' % sent_percentage, | 418 sent_percentage = percentage |
395 sys.stdout.flush() | 419 print '\rSent %d%%' % sent_percentage, |
420 sys.stdout.flush() | |
421 finally: | |
422 if fp: | |
423 fp.close() | |
Paul Stewart
2010/11/17 21:37:36
If you can't get it working, please revert this ch
| |
396 if self.verbose: | 424 if self.verbose: |
397 print '\n' | 425 print '\n' |
398 f.close() | 426 f.close() |
399 | 427 |
400 | 428 |
401 class StringUpdateResponse(UpdateResponse): | 429 class StringUpdateResponse(UpdateResponse): |
402 """Respond by sending the contents of a string.""" | 430 """Respond by sending the contents of a string.""" |
403 | 431 |
404 def __init__(self, string, content_type='text/plain'): | 432 def __init__(self, string, content_type='text/plain'): |
405 self.string = string | 433 self.string = string |
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
549 usage = 'usage: %prog [options]' | 577 usage = 'usage: %prog [options]' |
550 parser = optparse.OptionParser(usage=usage) | 578 parser = optparse.OptionParser(usage=usage) |
551 parser.add_option('--board', dest='board', default=None, | 579 parser.add_option('--board', dest='board', default=None, |
552 help='Board platform type') | 580 help='Board platform type') |
553 parser.add_option('--force-mismatch', dest='force_mismatch', default=False, | 581 parser.add_option('--force-mismatch', dest='force_mismatch', default=False, |
554 action='store_true', | 582 action='store_true', |
555 help='Upgrade even if client arch does not match') | 583 help='Upgrade even if client arch does not match') |
556 parser.add_option('--from', dest='src', default=None, | 584 parser.add_option('--from', dest='src', default=None, |
557 help='Source image to install') | 585 help='Source image to install') |
558 parser.add_option('--image-name', dest='image_name', | 586 parser.add_option('--image-name', dest='image_name', |
559 default=DEFAULT_IMAGE_NAME, | |
560 help='Filename within image directory to load') | 587 help='Filename within image directory to load') |
561 parser.add_option('--port', dest='port', default=8081, type='int', | 588 parser.add_option('--port', dest='port', default=8081, type='int', |
562 help='TCP port to serve from and tunnel through') | 589 help='TCP port to serve from and tunnel through') |
563 parser.add_option('--remote', dest='remote', default=None, | 590 parser.add_option('--remote', dest='remote', default=None, |
564 help='Remote device-under-test IP address') | 591 help='Remote device-under-test IP address') |
565 parser.add_option('--server-only', dest='server_only', default=False, | 592 parser.add_option('--server-only', dest='server_only', default=False, |
566 action='store_true', help='Do not start client') | 593 action='store_true', help='Do not start client') |
567 parser.add_option('--verbose', dest='verbose', default=False, | 594 parser.add_option('--verbose', dest='verbose', default=False, |
595 action='store_true', help='Display progress') | |
596 parser.add_option('--debug', dest='debug', default=False, | |
568 action='store_true', help='Display running commands') | 597 action='store_true', help='Display running commands') |
598 parser.add_option('--test', dest='test', default=False, | |
599 action='store_true', help='Select test image') | |
569 | 600 |
570 (options, args) = parser.parse_args(argv) | 601 (options, args) = parser.parse_args(argv) |
571 | 602 |
572 cros_env = CrosEnv(verbose=options.verbose) | 603 # we can build the test image if it doesn't exist, so remember if we want to |
604 maybe_build_test = False | |
adlr
2010/11/17 20:43:25
it would be nice to have a variable name that more
| |
605 | |
606 verbosity = 0 | |
607 if options.verbose: | |
608 verbosity = 1 | |
609 if options.debug: | |
610 verbosity = 2 | |
611 cros_env = CrosEnv(verbose=verbosity) | |
573 | 612 |
574 if not options.board: | 613 if not options.board: |
575 options.board = cros_env.GetDefaultBoard() | 614 options.board = cros_env.GetDefaultBoard() |
576 | 615 |
577 if not options.src: | 616 if not options.src: |
578 options.src = cros_env.GetLatestImage(options.board) | 617 options.src = cros_env.GetLatestImage(options.board) |
579 if options.src is None: | 618 if options.src is None: |
580 parser.error('No --from argument given and no default image found') | 619 parser.error('No --from argument given and no default image found') |
581 | 620 |
582 cros_env.Info('Performing update from %s' % options.src) | 621 cros_env.Info('Performing update from %s' % options.src) |
583 | 622 |
584 if not os.path.exists(options.src): | 623 if not os.path.exists(options.src): |
585 parser.error('Path %s does not exist' % options.src) | 624 parser.error('Path %s does not exist' % options.src) |
586 | 625 |
626 if not options.image_name: | |
627 # auto-select the correct image | |
628 if options.test: | |
629 options.image_name = DEFAULT_IMAGE_NAME_TEST | |
630 | |
631 # we will build the test image if not found | |
632 maybe_build_test = True | |
633 else: | |
634 options.image_name = DEFAULT_IMAGE_NAME | |
635 | |
587 if os.path.isdir(options.src): | 636 if os.path.isdir(options.src): |
588 image_directory = options.src | 637 image_directory = options.src |
589 image_file = os.path.join(options.src, options.image_name) | 638 image_file = os.path.join(options.src, options.image_name) |
590 | 639 |
591 if not os.path.exists(image_file): | 640 if not os.path.exists(image_file): |
641 if maybe_build_test: | |
642 # we want a test image but it doesn't exist | |
643 # try to build it if we can | |
644 cros_env.Info('Creating test image') | |
645 test_output = cros_env.cmd.Output(cros_env.CrosUtilsPath('enter_chroot.s h'), | |
adlr
2010/11/17 20:43:25
a few lines around here (this, 646, 649) are over
| |
646 '--', './mod_image_for_test.sh', '--board=%s' % options.board, ' -y') | |
647 if not os.path.exists(image_file): | |
648 print test_output | |
649 cros_env.Fatal('Failed to create test image - please run ./mod_image_f or_test.sh manually inside the chroot') | |
592 parser.error('Image file %s does not exist' % image_file) | 650 parser.error('Image file %s does not exist' % image_file) |
593 else: | 651 else: |
594 image_file = options.src | 652 image_file = options.src |
595 image_directory = os.path.dirname(options.src) | 653 image_directory = os.path.dirname(options.src) |
596 | 654 |
655 update_file = os.path.join(image_directory, UPDATE_FILENAME) | |
656 stateful_file = os.path.join(image_directory, STATEFUL_FILENAME) | |
657 cros_env.Debug("Image file %s" % image_file) | |
658 cros_env.Debug("Update file %s" % update_file) | |
659 cros_env.Debug("Stateful file %s" % stateful_file) | |
660 | |
597 if options.remote: | 661 if options.remote: |
662 cros_env.Info('Contacting client %s' % options.remote) | |
598 cros_env.SetRemote(options.remote) | 663 cros_env.SetRemote(options.remote) |
599 rel = cros_env.GetRemoteRelease() | 664 rel = cros_env.GetRemoteRelease() |
600 if not rel: | 665 if not rel: |
601 cros_env.Fatal('Could not retrieve remote lsb-release') | 666 cros_env.Fatal('Could not retrieve remote lsb-release') |
602 board = rel.get('CHROMEOS_RELEASE_BOARD', '(None)') | 667 board = rel.get('CHROMEOS_RELEASE_BOARD', '(None)') |
603 if board != options.board and not options.force_mismatch: | 668 if board != options.board and not options.force_mismatch: |
604 cros_env.Error('Board %s does not match expected %s' % | 669 cros_env.Error('Board %s does not match expected %s' % |
605 (board, options.board)) | 670 (board, options.board)) |
606 cros_env.Error('(Use --force-mismatch option to override this)') | 671 cros_env.Error('(Use --force-mismatch option to override this)') |
607 cros_env.Fatal() | 672 cros_env.Fatal() |
608 | 673 |
609 elif not options.server_only: | 674 elif not options.server_only: |
610 parser.error('Either --server-only must be specified or ' | 675 parser.error('Either --server-only must be specified or ' |
611 '--remote=<client> needs to be given') | 676 '--remote=<client> needs to be given') |
612 | 677 |
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 | 678 if (not cros_env.GenerateUpdatePayload(image_file, update_file) or |
617 not cros_env.BuildStateful(image_file, stateful_file)): | 679 not cros_env.BuildStateful(image_file, stateful_file)): |
618 cros_env.Fatal() | 680 cros_env.Fatal() |
619 | 681 |
620 cros_env.CreateServer(options.port, update_file, stateful_file) | 682 cros_env.CreateServer(options.port, update_file, stateful_file) |
621 | 683 |
622 exit_status = 1 | 684 exit_status = 1 |
623 if options.server_only: | 685 if options.server_only: |
624 child = None | 686 child = None |
625 else: | 687 else: |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
657 | 719 |
658 if child: | 720 if child: |
659 os.kill(child, 15) | 721 os.kill(child, 15) |
660 | 722 |
661 cros_env.Info('Server exiting with status %d' % exit_status) | 723 cros_env.Info('Server exiting with status %d' % exit_status) |
662 sys.exit(exit_status) | 724 sys.exit(exit_status) |
663 | 725 |
664 | 726 |
665 if __name__ == '__main__': | 727 if __name__ == '__main__': |
666 main(sys.argv) | 728 main(sys.argv) |
OLD | NEW |