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 |