OLD | NEW |
1 # Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved. | 1 # Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 from buildutil import BuildObject | 5 from buildutil import BuildObject |
6 from xml.dom import minidom | 6 from xml.dom import minidom |
7 | 7 |
8 import cherrypy | 8 import cherrypy |
9 import os | 9 import os |
10 import shutil | 10 import shutil |
11 import subprocess | 11 import subprocess |
12 import time | 12 import time |
13 import urlparse | 13 import urlparse |
14 | 14 |
15 | 15 |
16 def _LogMessage(message): | 16 def _LogMessage(message): |
17 cherrypy.log(message, 'UPDATE') | 17 cherrypy.log(message, 'UPDATE') |
18 | 18 |
19 UPDATE_FILE='update.gz' | 19 UPDATE_FILE = 'update.gz' |
20 STATEFUL_FILE='stateful.tgz' | 20 STATEFUL_FILE = 'stateful.tgz' |
| 21 CACHE_DIR = 'cache' |
21 | 22 |
22 | 23 |
23 def _ChangeUrlPort(url, new_port): | 24 def _ChangeUrlPort(url, new_port): |
24 """Return the URL passed in with a different port""" | 25 """Return the URL passed in with a different port""" |
25 scheme, netloc, path, query, fragment = urlparse.urlsplit(url) | 26 scheme, netloc, path, query, fragment = urlparse.urlsplit(url) |
26 host_port = netloc.split(':') | 27 host_port = netloc.split(':') |
27 | 28 |
28 if len(host_port) == 1: | 29 if len(host_port) == 1: |
29 host_port.append(new_port) | 30 host_port.append(new_port) |
30 else: | 31 else: |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 | 76 |
76 self.client_prefix = client_prefix | 77 self.client_prefix = client_prefix |
77 self.forced_image = forced_image | 78 self.forced_image = forced_image |
78 self.forced_payload = forced_payload | 79 self.forced_payload = forced_payload |
79 self.src_image = src_image | 80 self.src_image = src_image |
80 self.proxy_port = proxy_port | 81 self.proxy_port = proxy_port |
81 self.vm = vm | 82 self.vm = vm |
82 self.board = board | 83 self.board = board |
83 self.copy_to_static_root = copy_to_static_root | 84 self.copy_to_static_root = copy_to_static_root |
84 | 85 |
85 # Track update pregeneration, so we don't recopy if not needed. | 86 # Path to pre-generated file. |
86 self.pregenerated = False | 87 self.pregenerated_path = None |
87 | 88 |
88 def _GetSecondsSinceMidnight(self): | 89 def _GetSecondsSinceMidnight(self): |
89 """Returns the seconds since midnight as a decimal value.""" | 90 """Returns the seconds since midnight as a decimal value.""" |
90 now = time.localtime() | 91 now = time.localtime() |
91 return now[3] * 3600 + now[4] * 60 + now[5] | 92 return now[3] * 3600 + now[4] * 60 + now[5] |
92 | 93 |
93 def _GetDefaultBoardID(self): | 94 def _GetDefaultBoardID(self): |
94 """Returns the default board id stored in .default_board.""" | 95 """Returns the default board id stored in .default_board.""" |
95 board_file = '%s/.default_board' % (self.scripts_dir) | 96 board_file = '%s/.default_board' % (self.scripts_dir) |
96 try: | 97 try: |
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
245 if self.vm: | 246 if self.vm: |
246 patch_kernel_flag = '' | 247 patch_kernel_flag = '' |
247 | 248 |
248 mkupdate_command = ( | 249 mkupdate_command = ( |
249 '%s/cros_generate_update_payload --image="%s" --output="%s" ' | 250 '%s/cros_generate_update_payload --image="%s" --output="%s" ' |
250 '%s --noold_style --src_image="%s"' % ( | 251 '%s --noold_style --src_image="%s"' % ( |
251 self.scripts_dir, image_path, update_path, patch_kernel_flag, | 252 self.scripts_dir, image_path, update_path, patch_kernel_flag, |
252 src_image)) | 253 src_image)) |
253 _LogMessage(mkupdate_command) | 254 _LogMessage(mkupdate_command) |
254 if os.system(mkupdate_command) != 0: | 255 if os.system(mkupdate_command) != 0: |
255 _LogMessage('Failed to create base update file') | 256 _LogMessage('Failed to create update payload') |
256 return None | 257 return None |
257 | 258 |
258 return UPDATE_FILE | 259 return UPDATE_FILE |
259 | 260 |
260 def GenerateStatefulFile(self, image_path, output_dir): | 261 def GenerateStatefulFile(self, image_path, output_dir): |
261 """Generates a stateful update payload given a full path to an image. | 262 """Generates a stateful update payload given a full path to an image. |
262 | 263 |
263 Args: | 264 Args: |
264 image_path: Full path to image. | 265 image_path: Full path to image. |
265 Returns: | 266 Returns: |
(...skipping 11 matching lines...) Expand all Loading... |
277 return STATEFUL_FILE | 278 return STATEFUL_FILE |
278 | 279 |
279 def FindCachedUpdateImageSubDir(self, src_image, dest_image): | 280 def FindCachedUpdateImageSubDir(self, src_image, dest_image): |
280 """Find directory to store a cached update. | 281 """Find directory to store a cached update. |
281 | 282 |
282 Given one, or two images for an update, this finds which | 283 Given one, or two images for an update, this finds which |
283 cache directory should hold the update files, even if they don't exist | 284 cache directory should hold the update files, even if they don't exist |
284 yet. The directory will be inside static_image_dir, and of the form: | 285 yet. The directory will be inside static_image_dir, and of the form: |
285 | 286 |
286 Non-delta updates: | 287 Non-delta updates: |
287 cache/12345678 | 288 CACHE_DIR/12345678 |
288 | 289 |
289 Delta updates: | 290 Delta updates: |
290 cache/12345678_12345678 | 291 CACHE_DIR/12345678_12345678 |
291 """ | 292 """ |
292 # If there is no src, we only have an image file, check image for changes | 293 # If there is no src, we only have an image file, check image for changes |
293 if not src_image: | 294 if not src_image: |
294 return os.path.join('cache', self._GetMd5(dest_image)) | 295 return os.path.join(CACHE_DIR, self._GetMd5(dest_image)) |
295 | 296 |
296 # If we have src and dest, we are a delta, and check both for changes | 297 # If we have src and dest, we are a delta, and check both for changes |
297 return os.path.join('cache', | 298 return os.path.join(CACHE_DIR, |
298 "%s_%s" % (self._GetMd5(src_image), | 299 "%s_%s" % (self._GetMd5(src_image), |
299 self._GetMd5(dest_image))) | 300 self._GetMd5(dest_image))) |
300 | 301 |
301 def GenerateUpdateImage(self, image_path, output_dir): | 302 def GenerateUpdateImage(self, image_path, output_dir): |
302 """Force generates an update payload based on the given image_path. | 303 """Force generates an update payload based on the given image_path. |
303 | 304 |
304 Args: | 305 Args: |
305 src_image: image we are updating from (Null/empty for non-delta) | 306 src_image: image we are updating from (Null/empty for non-delta) |
306 image_path: full path to the image. | 307 image_path: full path to the image. |
307 output_dir: the directory to write the update payloads in | 308 output_dir: the directory to write the update payloads in |
308 Returns: | 309 Returns: |
309 update payload name relative to output_dir | 310 update payload name relative to output_dir |
310 """ | 311 """ |
311 update_file = None | 312 update_file = None |
312 stateful_update_file = None | 313 stateful_update_file = None |
313 | 314 |
314 # Actually do the generation | 315 # Actually do the generation |
315 _LogMessage('Generating update for image %s' % image_path) | 316 _LogMessage('Generating update for image %s' % image_path) |
316 update_file = self.GenerateUpdateFile(self.src_image, | 317 update_file = self.GenerateUpdateFile(self.src_image, |
317 image_path, | 318 image_path, |
318 output_dir) | 319 output_dir) |
319 | 320 |
320 if update_file: | 321 if update_file: |
321 stateful_update_file = self.GenerateStatefulFile(image_path, | 322 stateful_update_file = self.GenerateStatefulFile(image_path, |
322 output_dir) | 323 output_dir) |
323 | 324 |
324 if update_file and stateful_update_file: | 325 if update_file and stateful_update_file: |
325 return update_file | 326 return update_file |
326 | 327 else: |
327 _LogMessage('Failed to generate update') | 328 _LogMessage('Failed to generate update.') |
328 | 329 return None |
329 # Cleanup incomplete files, if they exist | |
330 if update_file and os.path.exists(update_file): | |
331 os.remove(update_file) | |
332 | |
333 return None | |
334 | 330 |
335 def GenerateUpdateImageWithCache(self, image_path, static_image_dir): | 331 def GenerateUpdateImageWithCache(self, image_path, static_image_dir): |
336 """Force generates an update payload based on the given image_path. | 332 """Force generates an update payload based on the given image_path. |
337 | 333 |
338 Args: | 334 Args: |
339 image_path: full path to the image. | 335 image_path: full path to the image. |
340 static_image_dir: the directory to move images to after generating. | 336 static_image_dir: the directory to move images to after generating. |
341 Returns: | 337 Returns: |
342 update filename (not directory) relative to static_image_dir on success, | 338 update filename (not directory) relative to static_image_dir on success, |
343 or None | 339 or None. |
344 """ | 340 """ |
345 _LogMessage('Generating update for src %s image %s' % (self.src_image, | 341 _LogMessage('Generating update for src %s image %s' % (self.src_image, |
346 image_path)) | 342 image_path)) |
347 | 343 |
348 # If it was pregenerated, don't regenerate | 344 # If it was pregenerated_path, don't regenerate |
349 if self.pregenerated: | 345 if self.pregenerated_path: |
350 return UPDATE_FILE | 346 return self.pregenerated_path |
351 | 347 |
352 # Which sub_dir of static_image_dir should hold our cached update image | 348 # Which sub_dir of static_image_dir should hold our cached update image |
353 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path) | 349 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path) |
354 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir) | 350 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir) |
355 | 351 |
| 352 update_path = os.path.join(cache_sub_dir, UPDATE_FILE) |
| 353 |
356 # The cached payloads exist in a cache dir | 354 # The cached payloads exist in a cache dir |
357 cache_update_payload = os.path.join(static_image_dir, | 355 cache_update_payload = os.path.join(static_image_dir, |
358 cache_sub_dir, | 356 update_path) |
359 UPDATE_FILE) | |
360 cache_stateful_payload = os.path.join(static_image_dir, | 357 cache_stateful_payload = os.path.join(static_image_dir, |
361 cache_sub_dir, | 358 cache_sub_dir, |
362 STATEFUL_FILE) | 359 STATEFUL_FILE) |
363 | 360 |
364 # The final results exist directly in static | 361 # Check to see if this cache directory is valid. |
365 update_payload = os.path.join(static_image_dir, | 362 if not os.path.exists(cache_update_payload) or not os.path.exists( |
366 UPDATE_FILE) | 363 cache_stateful_payload): |
367 stateful_payload = os.path.join(static_image_dir, | 364 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir) |
368 STATEFUL_FILE) | 365 # Clean up stale state. |
| 366 os.system('rm -rf "%s"' % full_cache_dir) |
| 367 os.makedirs(full_cache_dir) |
| 368 return_path = self.GenerateUpdateImage(image_path, |
| 369 full_cache_dir) |
369 | 370 |
370 # If there isn't a cached payload, make one | 371 # Clean up cache dir since it's not valid. |
371 if not os.path.exists(cache_update_payload): | 372 if not return_path: |
372 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir) | 373 os.system('rm -rf "%s"' % full_cache_dir) |
| 374 return None |
| 375 else: |
| 376 assert (return_path == update_path, |
| 377 'Returned path %s not equal to %s' % (return_path, update_path)) |
373 | 378 |
374 # Create the directory for the cache values | 379 self.pregenerated_path = update_path |
375 if not os.path.exists(full_cache_dir): | |
376 os.makedirs(full_cache_dir) | |
377 | |
378 result = self.GenerateUpdateImage(image_path, | |
379 full_cache_dir) | |
380 | |
381 if not result: | |
382 # Clean up cache dir if it's not valid | |
383 os.system("rm -rf %s" % os.path.join(static_image_dir, cache_sub_dir)) | |
384 return None | |
385 | 380 |
386 # Generation complete, copy if requested. | 381 # Generation complete, copy if requested. |
387 if self.copy_to_static_root: | 382 if self.copy_to_static_root: |
| 383 # The final results exist directly in static |
| 384 update_payload = os.path.join(static_image_dir, |
| 385 UPDATE_FILE) |
| 386 stateful_payload = os.path.join(static_image_dir, |
| 387 STATEFUL_FILE) |
388 self._Copy(cache_update_payload, update_payload) | 388 self._Copy(cache_update_payload, update_payload) |
389 self._Copy(cache_stateful_payload, stateful_payload) | 389 self._Copy(cache_stateful_payload, stateful_payload) |
390 | 390 return UPDATE_FILE |
391 # Return just the filename in static_image_dir. | 391 else: |
392 return UPDATE_FILE | 392 return self.pregenerated_path |
393 | 393 |
394 def GenerateLatestUpdateImage(self, board_id, client_version, | 394 def GenerateLatestUpdateImage(self, board_id, client_version, |
395 static_image_dir): | 395 static_image_dir): |
396 """Generates an update using the latest image that has been built. | 396 """Generates an update using the latest image that has been built. |
397 | 397 |
398 This will only generate an update if the newest update is newer than that | 398 This will only generate an update if the newest update is newer than that |
399 on the client or client_version is 'ForcedUpdate'. | 399 on the client or client_version is 'ForcedUpdate'. |
400 | 400 |
401 Args: | 401 Args: |
402 board_id: Name of the board. | 402 board_id: Name of the board. |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
521 Returns: | 521 Returns: |
522 file name relative to static_image_dir on success. | 522 file name relative to static_image_dir on success. |
523 """ | 523 """ |
524 dest_path = os.path.join(static_image_dir, UPDATE_FILE) | 524 dest_path = os.path.join(static_image_dir, UPDATE_FILE) |
525 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE) | 525 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE) |
526 | 526 |
527 if self.forced_payload: | 527 if self.forced_payload: |
528 # If the forced payload is not already in our static_image_dir, | 528 # If the forced payload is not already in our static_image_dir, |
529 # copy it there. | 529 # copy it there. |
530 src_path = os.path.abspath(self.forced_payload) | 530 src_path = os.path.abspath(self.forced_payload) |
531 | |
532 src_stateful = os.path.join(os.path.dirname(src_path), | 531 src_stateful = os.path.join(os.path.dirname(src_path), |
533 STATEFUL_FILE) | 532 STATEFUL_FILE) |
534 | 533 |
535 # Only copy the files if the source directory is different from dest. | 534 # Only copy the files if the source directory is different from dest. |
536 if os.path.dirname(src_path) != os.path.abspath(static_image_dir): | 535 if os.path.dirname(src_path) != os.path.abspath(static_image_dir): |
537 self._Copy(src_path, dest_path) | 536 self._Copy(src_path, dest_path) |
538 | 537 |
539 # The stateful payload is optional. | 538 # The stateful payload is optional. |
540 if os.path.exists(src_stateful): | 539 if os.path.exists(src_stateful): |
541 self._Copy(src_stateful, dest_stateful) | 540 self._Copy(src_stateful, dest_stateful) |
(...skipping 18 matching lines...) Expand all Loading... |
560 _LogMessage('WARN: %s not found. Expected for dev and test builds.' % | 559 _LogMessage('WARN: %s not found. Expected for dev and test builds.' % |
561 STATEFUL_FILE) | 560 STATEFUL_FILE) |
562 | 561 |
563 return UPDATE_FILE | 562 return UPDATE_FILE |
564 else: | 563 else: |
565 if board_id: | 564 if board_id: |
566 return self.GenerateLatestUpdateImage(board_id, | 565 return self.GenerateLatestUpdateImage(board_id, |
567 client_version, | 566 client_version, |
568 static_image_dir) | 567 static_image_dir) |
569 | 568 |
570 _LogMessage('You must set --board for pre-generating latest update.') | 569 _LogMessage('Failed to genereate update. ' |
| 570 'You must set --board when pre-generating latest update.') |
571 return None | 571 return None |
572 | 572 |
573 def PreGenerateUpdate(self): | 573 def PreGenerateUpdate(self): |
574 """Pre-generates an update. Returns True on success. | 574 """Pre-generates an update and prints out the relative path it. |
| 575 |
| 576 Returns relative path of the update on success. |
575 """ | 577 """ |
576 # Does not work with factory config. | 578 # Does not work with factory config. |
577 assert(not self.factory_config) | 579 assert(not self.factory_config) |
578 _LogMessage('Pre-generating the update payload.') | 580 _LogMessage('Pre-generating the update payload.') |
579 # Does not work with labels so just use static dir. | 581 # Does not work with labels so just use static dir. |
580 if self.GenerateUpdatePayloadForNonFactory(self.board, '0.0.0.0', | 582 pregenerated_update = self.GenerateUpdatePayloadForNonFactory( |
581 self.static_dir): | 583 self.board, '0.0.0.0', self.static_dir) |
582 _LogMessage('Pre-generated update successfully.') | 584 if pregenerated_update: |
583 self.pregenerated = True | 585 print 'PREGENERATED_UPDATE=%s' % pregenerated_update |
584 return True | 586 |
585 else: | 587 return pregenerated_update |
586 _LogMessage('Failed to pre-generate update.') | |
587 return False | |
588 | 588 |
589 def HandleUpdatePing(self, data, label=None): | 589 def HandleUpdatePing(self, data, label=None): |
590 """Handles an update ping from an update client. | 590 """Handles an update ping from an update client. |
591 | 591 |
592 Args: | 592 Args: |
593 data: xml blob from client. | 593 data: xml blob from client. |
594 label: optional label for the update. | 594 label: optional label for the update. |
595 Returns: | 595 Returns: |
596 Update payload message for client. | 596 Update payload message for client. |
597 """ | 597 """ |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
657 is_delta_format = self._IsDeltaFormatFile(filename) | 657 is_delta_format = self._IsDeltaFormatFile(filename) |
658 if label: | 658 if label: |
659 url = '%s/%s/%s' % (static_urlbase, label, payload_path) | 659 url = '%s/%s/%s' % (static_urlbase, label, payload_path) |
660 else: | 660 else: |
661 url = '%s/%s' % (static_urlbase, payload_path) | 661 url = '%s/%s' % (static_urlbase, payload_path) |
662 | 662 |
663 _LogMessage('Responding to client to use url %s to get image.' % url) | 663 _LogMessage('Responding to client to use url %s to get image.' % url) |
664 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format) | 664 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format) |
665 else: | 665 else: |
666 return self.GetNoUpdatePayload() | 666 return self.GetNoUpdatePayload() |
OLD | NEW |