| 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 | 13 |
| 14 | 14 |
| 15 def _LogMessage(message): | 15 def _LogMessage(message): |
| 16 cherrypy.log(message, 'UPDATE') | 16 cherrypy.log(message, 'UPDATE') |
| 17 | 17 |
| 18 UPDATE_FILE='update.gz' | 18 UPDATE_FILE='update.gz' |
| 19 STATEFUL_FILE='stateful.tgz' | 19 STATEFUL_FILE='stateful.tgz' |
| 20 | 20 |
| 21 class Autoupdate(BuildObject): | 21 class Autoupdate(BuildObject): |
| 22 """Class that contains functionality that handles Chrome OS update pings. | 22 """Class that contains functionality that handles Chrome OS update pings. |
| 23 | 23 |
| 24 Members: | 24 Members: |
| 25 serve_only: Serve images from a pre-built image.zip file. static_dir | 25 serve_only: Serve only pre-built updates. static_dir must contain update.gz |
| 26 must be set to the location of the image.zip. | 26 and stateful.tgz. |
| 27 factory_config: Path to the factory config file if handling factory | 27 factory_config: Path to the factory config file if handling factory |
| 28 requests. | 28 requests. |
| 29 use_test_image: Use chromiumos_test_image.bin rather than the standard. | 29 use_test_image: Use chromiumos_test_image.bin rather than the standard. |
| 30 static_url_base: base URL, other than devserver, for update images. | 30 static_url_base: base URL, other than devserver, for update images. |
| 31 client_prefix: The prefix for the update engine client. | 31 client_prefix: The prefix for the update engine client. |
| 32 forced_image: Path to an image to use for all updates. | 32 forced_image: Path to an image to use for all updates. |
| 33 """ | 33 """ |
| 34 | 34 |
| 35 def __init__(self, serve_only=None, test_image=False, urlbase=None, | 35 def __init__(self, serve_only=None, test_image=False, urlbase=None, |
| 36 factory_config_path=None, client_prefix=None, | 36 factory_config_path=None, client_prefix=None, |
| 37 forced_image=None, forced_payload=None, | 37 forced_image=None, forced_payload=None, |
| 38 port=8080, src_image='', vm=False, board=None, | 38 port=8080, src_image='', vm=False, board=None, |
| 39 *args, **kwargs): | 39 *args, **kwargs): |
| 40 super(Autoupdate, self).__init__(*args, **kwargs) | 40 super(Autoupdate, self).__init__(*args, **kwargs) |
| 41 self.serve_only = serve_only | 41 self.serve_only = serve_only |
| 42 self.factory_config = factory_config_path | 42 self.factory_config = factory_config_path |
| 43 self.use_test_image = test_image | 43 self.use_test_image = test_image |
| 44 if urlbase: | 44 if urlbase: |
| 45 self.urlbase = urlbase | 45 self.urlbase = urlbase |
| 46 else: | 46 else: |
| 47 self.urlbase = None | 47 self.urlbase = None |
| 48 | 48 |
| 49 self.client_prefix = client_prefix | 49 self.client_prefix = client_prefix |
| 50 self.forced_image = forced_image | 50 self.forced_image = forced_image |
| 51 self.forced_payload = forced_payload | 51 self.forced_payload = forced_payload |
| 52 self.src_image = src_image | 52 self.src_image = src_image |
| 53 self.vm = vm | 53 self.vm = vm |
| 54 self.board = board | 54 self.board = board |
| 55 | 55 |
| 56 # Caching is enabled if we are not doing serve_only | |
| 57 # aka if --archive_dir was not passed in. | |
| 58 self.caching_enabled = not self.serve_only | |
| 59 | |
| 60 # Track update pregeneration, so we don't recopy if not needed. | 56 # Track update pregeneration, so we don't recopy if not needed. |
| 61 self.pregenerated = False | 57 self.pregenerated = False |
| 62 | 58 |
| 63 def _GetSecondsSinceMidnight(self): | 59 def _GetSecondsSinceMidnight(self): |
| 64 """Returns the seconds since midnight as a decimal value.""" | 60 """Returns the seconds since midnight as a decimal value.""" |
| 65 now = time.localtime() | 61 now = time.localtime() |
| 66 return now[3] * 3600 + now[4] * 60 + now[5] | 62 return now[3] * 3600 + now[4] * 60 + now[5] |
| 67 | 63 |
| 68 def _GetDefaultBoardID(self): | 64 def _GetDefaultBoardID(self): |
| 69 """Returns the default board id stored in .default_board.""" | 65 """Returns the default board id stored in .default_board.""" |
| (...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 317 update filename (not directory) relative to static_image_dir on success, | 313 update filename (not directory) relative to static_image_dir on success, |
| 318 or None | 314 or None |
| 319 """ | 315 """ |
| 320 _LogMessage('Generating update for src %s image %s' % (self.src_image, | 316 _LogMessage('Generating update for src %s image %s' % (self.src_image, |
| 321 image_path)) | 317 image_path)) |
| 322 | 318 |
| 323 # If it was pregenerated, don't regenerate | 319 # If it was pregenerated, don't regenerate |
| 324 if self.pregenerated: | 320 if self.pregenerated: |
| 325 return UPDATE_FILE | 321 return UPDATE_FILE |
| 326 | 322 |
| 327 if not self.caching_enabled: | |
| 328 return self.GenerateUpdateImage(image_path, static_image_dir) | |
| 329 | |
| 330 # Which sub_dir of static_image_dir should hold our cached update image | 323 # Which sub_dir of static_image_dir should hold our cached update image |
| 331 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path) | 324 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path) |
| 332 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir) | 325 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir) |
| 333 | 326 |
| 334 # The cached payloads exist in a cache dir | 327 # The cached payloads exist in a cache dir |
| 335 cache_update_payload = os.path.join(static_image_dir, | 328 cache_update_payload = os.path.join(static_image_dir, |
| 336 cache_sub_dir, | 329 cache_sub_dir, |
| 337 UPDATE_FILE) | 330 UPDATE_FILE) |
| 338 cache_stateful_payload = os.path.join(static_image_dir, | 331 cache_stateful_payload = os.path.join(static_image_dir, |
| 339 cache_sub_dir, | 332 cache_sub_dir, |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 391 | 384 |
| 392 # Check to see whether or not we should update. | 385 # Check to see whether or not we should update. |
| 393 if client_version != 'ForcedUpdate' and not self._CanUpdate( | 386 if client_version != 'ForcedUpdate' and not self._CanUpdate( |
| 394 client_version, latest_version): | 387 client_version, latest_version): |
| 395 _LogMessage('no update') | 388 _LogMessage('no update') |
| 396 return None | 389 return None |
| 397 | 390 |
| 398 return self.GenerateUpdateImageWithCache(latest_image_path, | 391 return self.GenerateUpdateImageWithCache(latest_image_path, |
| 399 static_image_dir=static_image_dir) | 392 static_image_dir=static_image_dir) |
| 400 | 393 |
| 401 def GenerateImageFromZip(self, static_image_dir): | |
| 402 """Generates an update from an image zip file. | |
| 403 | |
| 404 This method assumes you have an image.zip in directory you are serving | |
| 405 from. If this file is newer than a previously cached file, it will unzip | |
| 406 this file, create a payload and serve it. | |
| 407 | |
| 408 Args: | |
| 409 static_image_dir: Directory where the zip file exists. | |
| 410 Returns: | |
| 411 Name of the update payload relative to static_image_dir if successful. | |
| 412 """ | |
| 413 _LogMessage('Preparing to generate update from zip in %s.' % | |
| 414 static_image_dir) | |
| 415 image_path = os.path.join(static_image_dir, self._GetImageName()) | |
| 416 zip_file_path = os.path.join(static_image_dir, 'image.zip') | |
| 417 | |
| 418 # TODO(dgarrett): Either work caching into this path before | |
| 419 # we unpack, or remove zip support (sosa is considering). | |
| 420 # It does currently cache, but after the unpack. | |
| 421 | |
| 422 if not self._UnpackZip(static_image_dir): | |
| 423 _LogMessage('unzip image.zip failed.') | |
| 424 return None | |
| 425 | |
| 426 return self.GenerateUpdateImageWithCache(image_path, | |
| 427 static_image_dir=static_image_dir) | |
| 428 | |
| 429 def ImportFactoryConfigFile(self, filename, validate_checksums=False): | 394 def ImportFactoryConfigFile(self, filename, validate_checksums=False): |
| 430 """Imports a factory-floor server configuration file. The file should | 395 """Imports a factory-floor server configuration file. The file should |
| 431 be in this format: | 396 be in this format: |
| 432 config = [ | 397 config = [ |
| 433 { | 398 { |
| 434 'qual_ids': set([1, 2, 3, "x86-generic"]), | 399 'qual_ids': set([1, 2, 3, "x86-generic"]), |
| 435 'factory_image': 'generic-factory.gz', | 400 'factory_image': 'generic-factory.gz', |
| 436 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', | 401 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', |
| 437 'release_image': 'generic-release.gz', | 402 'release_image': 'generic-release.gz', |
| 438 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', | 403 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 519 # setting sha-256 to an empty string. | 484 # setting sha-256 to an empty string. |
| 520 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format) | 485 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format) |
| 521 | 486 |
| 522 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version, | 487 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version, |
| 523 static_image_dir): | 488 static_image_dir): |
| 524 """Generates an update for non-factory image. | 489 """Generates an update for non-factory image. |
| 525 | 490 |
| 526 Returns: | 491 Returns: |
| 527 file name relative to static_image_dir on success. | 492 file name relative to static_image_dir on success. |
| 528 """ | 493 """ |
| 494 dest_path = os.path.join(static_image_dir, UPDATE_FILE) |
| 495 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE) |
| 496 |
| 529 if self.forced_payload: | 497 if self.forced_payload: |
| 530 # If the forced payload is not already in our static_image_dir, | 498 # If the forced payload is not already in our static_image_dir, |
| 531 # copy it there. | 499 # copy it there. |
| 532 src_path = os.path.abspath(self.forced_payload) | 500 src_path = os.path.abspath(self.forced_payload) |
| 533 dest_path = os.path.join(static_image_dir, UPDATE_FILE) | |
| 534 | 501 |
| 535 src_stateful = os.path.join(os.path.dirname(src_path), | 502 src_stateful = os.path.join(os.path.dirname(src_path), |
| 536 STATEFUL_FILE) | 503 STATEFUL_FILE) |
| 537 dest_stateful = os.path.join(static_image_dir, | |
| 538 STATEFUL_FILE) | |
| 539 | 504 |
| 540 # Only copy the files if the source directory is different from dest. | 505 # Only copy the files if the source directory is different from dest. |
| 541 if os.path.dirname(src_path) != os.path.abspath(static_image_dir): | 506 if os.path.dirname(src_path) != os.path.abspath(static_image_dir): |
| 542 self._Copy(src_path, dest_path) | 507 self._Copy(src_path, dest_path) |
| 543 | 508 |
| 544 # The stateful payload is optional. | 509 # The stateful payload is optional. |
| 545 if os.path.exists(src_stateful): | 510 if os.path.exists(src_stateful): |
| 546 self._Copy(src_stateful, dest_stateful) | 511 self._Copy(src_stateful, dest_stateful) |
| 547 else: | 512 else: |
| 548 _LogMessage('WARN: %s not found. Expected for dev and test builds.' % | 513 _LogMessage('WARN: %s not found. Expected for dev and test builds.' % |
| 549 STATEFUL_FILE) | 514 STATEFUL_FILE) |
| 550 if os.path.exists(dest_stateful): | 515 if os.path.exists(dest_stateful): |
| 551 os.remove(dest_stateful) | 516 os.remove(dest_stateful) |
| 552 | 517 |
| 553 return UPDATE_FILE | 518 return UPDATE_FILE |
| 554 elif self.forced_image: | 519 elif self.forced_image: |
| 555 return self.GenerateUpdateImageWithCache( | 520 return self.GenerateUpdateImageWithCache( |
| 556 self.forced_image, | 521 self.forced_image, |
| 557 static_image_dir=static_image_dir) | 522 static_image_dir=static_image_dir) |
| 558 elif self.serve_only: | 523 elif self.serve_only: |
| 559 return self.GenerateImageFromZip(static_image_dir) | 524 # Warn if update or stateful files can't be found. |
| 525 if not os.path.exists(dest_path): |
| 526 _LogMessage('WARN: %s not found. Expected for dev and test builds.' % |
| 527 UPDATE_FILE) |
| 528 |
| 529 if not os.path.exists(dest_stateful): |
| 530 _LogMessage('WARN: %s not found. Expected for dev and test builds.' % |
| 531 STATEFUL_FILE) |
| 532 |
| 533 return UPDATE_FILE |
| 560 else: | 534 else: |
| 561 if board_id: | 535 if board_id: |
| 562 return self.GenerateLatestUpdateImage(board_id, | 536 return self.GenerateLatestUpdateImage(board_id, |
| 563 client_version, | 537 client_version, |
| 564 static_image_dir) | 538 static_image_dir) |
| 565 | 539 |
| 566 _LogMessage('You must set --board for pre-generating latest update.') | 540 _LogMessage('You must set --board for pre-generating latest update.') |
| 567 return None | 541 return None |
| 568 | 542 |
| 569 def PreGenerateUpdate(self): | 543 def PreGenerateUpdate(self): |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 648 is_delta_format = self._IsDeltaFormatFile(filename) | 622 is_delta_format = self._IsDeltaFormatFile(filename) |
| 649 if label: | 623 if label: |
| 650 url = '%s/%s/%s' % (static_urlbase, label, payload_path) | 624 url = '%s/%s/%s' % (static_urlbase, label, payload_path) |
| 651 else: | 625 else: |
| 652 url = '%s/%s' % (static_urlbase, payload_path) | 626 url = '%s/%s' % (static_urlbase, payload_path) |
| 653 | 627 |
| 654 _LogMessage('Responding to client to use url %s to get image.' % url) | 628 _LogMessage('Responding to client to use url %s to get image.' % url) |
| 655 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format) | 629 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format) |
| 656 else: | 630 else: |
| 657 return self.GetNoUpdatePayload() | 631 return self.GetNoUpdatePayload() |
| OLD | NEW |