| Index: autoupdate.py
|
| diff --git a/autoupdate.py b/autoupdate.py
|
| index c546018d9f70beeed87feae2041c8485eb74d936..07449664b559a424b8f5695e88607f0ecc5f494b 100644
|
| --- a/autoupdate.py
|
| +++ b/autoupdate.py
|
| @@ -1,4 +1,4 @@
|
| -# Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
|
| +# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
|
| # Use of this source code is governed by a BSD-style license that can be
|
| # found in the LICENSE file.
|
|
|
| @@ -7,88 +7,119 @@ from xml.dom import minidom
|
|
|
| import os
|
| import shutil
|
| +import sys
|
| import time
|
| import web
|
|
|
| -
|
| class Autoupdate(BuildObject):
|
| - """Class that contains functionality that handles Chrome OS update pings.
|
| -
|
| - Members:
|
| - serve_only: Serve images from a pre-built image.zip file. static_dir
|
| - must be set to the location of the image.zip.
|
| - factory_config: Path to the factory config file if handling factory
|
| - requests.
|
| - use_test_image: Use chromiumos_test_image.bin rather than the standard.
|
| - static_url_base: base URL, other than devserver, for update images.
|
| - client_prefix: The prefix for the update engine client.
|
| - forced_image: Path to an image to use for all updates.
|
| - """
|
| + # Basic functionality of handling ChromeOS autoupdate pings
|
| + # and building/serving update images.
|
| + # TODO(rtc): Clean this code up and write some tests.
|
|
|
| def __init__(self, serve_only=None, test_image=False, urlbase=None,
|
| - factory_config_path=None, client_prefix=None, forced_image=None,
|
| + factory_config_path=None, validate_factory_config=None,
|
| + client_prefix=None,
|
| *args, **kwargs):
|
| super(Autoupdate, self).__init__(*args, **kwargs)
|
| self.serve_only = serve_only
|
| self.factory_config = factory_config_path
|
| - self.use_test_image = test_image
|
| + self.test_image = test_image
|
| self.static_urlbase = urlbase
|
| self.client_prefix = client_prefix
|
| - self.forced_image = forced_image
|
| + if serve_only:
|
| + # If we're serving out of an archived build dir (e.g. a
|
| + # buildbot), prepare this webserver's magic 'static/' dir with a
|
| + # link to the build archive.
|
| + web.debug('Autoupdate in "serve update images only" mode.')
|
| + if os.path.exists('static/archive'):
|
| + if self.static_dir != os.readlink('static/archive'):
|
| + web.debug('removing stale symlink to %s' % self.static_dir)
|
| + os.unlink('static/archive')
|
| + os.symlink(self.static_dir, 'static/archive')
|
| + else:
|
| + os.symlink(self.static_dir, 'static/archive')
|
| + if factory_config_path is not None:
|
| + self.ImportFactoryConfigFile(factory_config_path, validate_factory_config)
|
|
|
| - def _GetSecondsSinceMidnight(self):
|
| - """Returns the seconds since midnight as a decimal value."""
|
| + def GetSecondsSinceMidnight(self):
|
| now = time.localtime()
|
| return now[3] * 3600 + now[4] * 60 + now[5]
|
|
|
| - def _GetDefaultBoardID(self):
|
| - """Returns the default board id stored in .default_board."""
|
| + def GetUpdatePayload(self, hash, size, url):
|
| + payload = """<?xml version="1.0" encoding="UTF-8"?>
|
| + <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
|
| + <daystart elapsed_seconds="%s"/>
|
| + <app appid="{%s}" status="ok">
|
| + <ping status="ok"/>
|
| + <updatecheck
|
| + codebase="%s"
|
| + hash="%s"
|
| + needsadmin="false"
|
| + size="%s"
|
| + status="ok"/>
|
| + </app>
|
| + </gupdate>
|
| + """
|
| + return payload % (self.GetSecondsSinceMidnight(),
|
| + self.app_id, url, hash, size)
|
| +
|
| + def GetNoUpdatePayload(self):
|
| + payload = """<?xml version="1.0" encoding="UTF-8"?>
|
| + <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
|
| + <daystart elapsed_seconds="%s"/>
|
| + <app appid="{%s}" status="ok">
|
| + <ping status="ok"/>
|
| + <updatecheck status="noupdate"/>
|
| + </app>
|
| + </gupdate>
|
| + """
|
| + return payload % (self.GetSecondsSinceMidnight(), self.app_id)
|
| +
|
| + def GetDefaultBoardID(self):
|
| board_file = '%s/.default_board' % (self.scripts_dir)
|
| try:
|
| return open(board_file).read()
|
| except IOError:
|
| return 'x86-generic'
|
|
|
| - def _GetLatestImageDir(self, board_id):
|
| - """Returns the latest image dir based on shell script."""
|
| + def GetLatestImagePath(self, board_id):
|
| cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
|
| return os.popen(cmd).read().strip()
|
|
|
| - def _GetVersionFromDir(self, image_dir):
|
| - """Returns the version of the image based on the name of the directory."""
|
| - latest_version = os.path.basename(image_dir)
|
| + def GetLatestVersion(self, latest_image_path):
|
| + latest_version = latest_image_path.split('/')[-1]
|
| +
|
| + # Removes the portage build prefix.
|
| + latest_version = latest_version.lstrip('g-')
|
| return latest_version.split('-')[0]
|
|
|
| - def _CanUpdate(self, client_version, latest_version):
|
| - """Returns true if the latest_version is greater than the client_version."""
|
| + def CanUpdate(self, client_version, latest_version):
|
| + """
|
| + Returns true iff the latest_version is greater than the client_version.
|
| + """
|
| client_tokens = client_version.replace('_', '').split('.')
|
| latest_tokens = latest_version.replace('_', '').split('.')
|
| - web.debug('client version %s latest version %s'
|
| - % (client_version, latest_version))
|
| + web.debug('client version %s latest version %s' \
|
| + % (client_version, latest_version))
|
| for i in range(4):
|
| if int(latest_tokens[i]) == int(client_tokens[i]):
|
| continue
|
| return int(latest_tokens[i]) > int(client_tokens[i])
|
| return False
|
|
|
| - def _UnpackStatefulPartition(self, image_path, stateful_file):
|
| - """Given an image, unpacks its stateful partition to stateful_file."""
|
| - image_dir = os.path.dirname(image_path)
|
| - image_file = os.path.basename(image_path)
|
| -
|
| + def UnpackStatefulPartition(self, image_path, image_file, stateful_file):
|
| + """Given an image, unpacks the stateful partition to stateful_file."""
|
| get_offset = '$(cgpt show -b -i 1 %s)' % image_file
|
| get_size = '$(cgpt show -s -i 1 %s)' % image_file
|
| unpack_command = (
|
| 'cd %s && '
|
| - 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
|
| - stateful_file, get_offset,
|
| - get_size))
|
| + 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_path, image_file,
|
| + stateful_file, get_offset, get_size))
|
| web.debug(unpack_command)
|
| return os.system(unpack_command) == 0
|
|
|
| - def _UnpackZip(self, image_dir):
|
| - """Unpacks an image.zip into a given directory."""
|
| - image = os.path.join(image_dir, self._GetImageName())
|
| + def UnpackZip(self, image_path, image_file):
|
| + image = os.path.join(image_path, image_file)
|
| if os.path.exists(image):
|
| return True
|
| else:
|
| @@ -96,231 +127,81 @@ class Autoupdate(BuildObject):
|
| # simultaneously by multiple request handlers. This means that
|
| # we're assuming each image.zip file lives in a versioned
|
| # directory (a la Buildbot).
|
| - return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
|
| + return os.system('cd %s && unzip -n image.zip %s unpack_partitions.sh' %
|
| + (image_path, image_file)) == 0
|
|
|
| - def _GetImageName(self):
|
| - """Returns the name of the image that should be used."""
|
| - if self.use_test_image:
|
| - image_name = 'chromiumos_test_image.bin'
|
| + def GetImageBinPath(self, image_path):
|
| + if self.test_image:
|
| + image_file = 'chromiumos_test_image.bin'
|
| else:
|
| - image_name = 'chromiumos_image.bin'
|
| - return image_name
|
| -
|
| - def _IsImageNewerThanCached(self, image_path, cached_file_path):
|
| - """Returns true if the image is newer than the cached image."""
|
| - if os.path.exists(cached_file_path) and os.path.exists(image_path):
|
| - web.debug('Usable cached image found.')
|
| - return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
|
| - elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
|
| - raise Exception('Image does not exist and cached image missing')
|
| - else:
|
| - # Only one is missing, figure out which one.
|
| - if os.path.exists(image_path):
|
| - web.debug('No cached image found - image generation required.')
|
| - return True
|
| - else:
|
| - web.debug('Only cached image found to serve.')
|
| - return False
|
| + image_file = 'chromiumos_image.bin'
|
| + return image_file
|
|
|
| - def _GetSize(self, update_path):
|
| - """Returns the size of the file given."""
|
| - return os.path.getsize(update_path)
|
| -
|
| - def _GetHash(self, update_path):
|
| - """Returns the sha1 of the file given."""
|
| - cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
|
| - % update_path)
|
| - return os.popen(cmd).read().rstrip()
|
| -
|
| - def GetUpdatePayload(self, hash, size, url):
|
| - """Returns a payload to the client corresponding to a new update.
|
| -
|
| - Args:
|
| - hash: hash of update blob
|
| - size: size of update blob
|
| - url: where to find update blob
|
| - Returns:
|
| - Xml string to be passed back to client.
|
| - """
|
| - payload = """<?xml version="1.0" encoding="UTF-8"?>
|
| - <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
|
| - <daystart elapsed_seconds="%s"/>
|
| - <app appid="{%s}" status="ok">
|
| - <ping status="ok"/>
|
| - <updatecheck
|
| - codebase="%s"
|
| - hash="%s"
|
| - needsadmin="false"
|
| - size="%s"
|
| - status="ok"/>
|
| - </app>
|
| - </gupdate>
|
| - """
|
| - return payload % (self._GetSecondsSinceMidnight(),
|
| - self.app_id, url, hash, size)
|
| + def BuildUpdateImage(self, image_path):
|
| + stateful_file = '%s/stateful.image' % image_path
|
| + image_file = self.GetImageBinPath(image_path)
|
| + bin_path = os.path.join(image_path, image_file)
|
|
|
| - def GetNoUpdatePayload(self):
|
| - """Returns a payload to the client corresponding to no update."""
|
| - payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
|
| - < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
|
| - < daystart elapsed_seconds = "%s" />
|
| - < app appid = "{%s}" status = "ok" >
|
| - < ping status = "ok" />
|
| - < updatecheck status = "noupdate" />
|
| - </ app >
|
| - </ gupdate >
|
| - """
|
| - return payload % (self._GetSecondsSinceMidnight(), self.app_id)
|
| -
|
| - def GenerateUpdateFile(self, image_path):
|
| - """Generates an update gz given a full path to an image.
|
| -
|
| - Args:
|
| - image_path: Full path to image.
|
| - Returns:
|
| - Path to created update_payload or None on error.
|
| - """
|
| - image_dir = os.path.dirname(image_path)
|
| - update_path = os.path.join(image_dir, 'update.gz')
|
| - web.debug('Generating update image %s' % update_path)
|
| -
|
| - mkupdate_command = (
|
| - '%s/cros_generate_update_payload --image=%s --output=%s '
|
| - '--patch_kernel' % (self.scripts_dir, image_path, update_path))
|
| - if os.system(mkupdate_command) != 0:
|
| - web.debug('Failed to create base update file')
|
| - return None
|
| -
|
| - return update_path
|
| -
|
| - def GenerateStatefulFile(self, image_path):
|
| - """Generates a stateful update gz given a full path to an image.
|
| -
|
| - Args:
|
| - image_path: Full path to image.
|
| - Returns:
|
| - Path to created stateful update_payload or None on error.
|
| - """
|
| - stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
|
| -
|
| - # Unpack to get stateful partition.
|
| - if self._UnpackStatefulPartition(image_path, stateful_partition_path):
|
| - mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
|
| - if os.system(mkstatefulupdate_command) == 0:
|
| - web.debug('Successfully generated %s.gz' % stateful_partition_path)
|
| - return '%s.gz' % stateful_partition_path
|
| -
|
| - web.debug('Failed to create stateful update file')
|
| - return None
|
| -
|
| - def MoveImagesToStaticDir(self, update_path, stateful_update_path,
|
| - static_image_dir):
|
| - """Moves gz files from their directories to serving directories.
|
| -
|
| - Args:
|
| - update_path: full path to main update gz.
|
| - stateful_update_path: full path to stateful partition gz.
|
| - static_image_dir: where to put files.
|
| - Returns:
|
| - Returns True if the files were moved over successfully.
|
| - """
|
| - try:
|
| - shutil.copy(update_path, static_image_dir)
|
| - shutil.copy(stateful_update_path, static_image_dir)
|
| - os.remove(update_path)
|
| - os.remove(stateful_update_path)
|
| - except Exception:
|
| - web.debug('Failed to move %s and %s to %s' % (update_path,
|
| - stateful_update_path,
|
| - static_image_dir))
|
| - return False
|
| -
|
| - return True
|
| -
|
| - def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
|
| - static_image_dir=None):
|
| - """Force generates an update payload based on the given image_path.
|
| -
|
| - Args:
|
| - image_path: full path to the image.
|
| - move_to_static_dir: Moves the files from their dir to the static dir.
|
| - static_image_dir: the directory to move images to after generating.
|
| - Returns:
|
| - True if the update payload was created successfully.
|
| - """
|
| - web.debug('Generating update for image %s' % image_path)
|
| - update_path = self.GenerateUpdateFile(image_path)
|
| - stateful_update_path = self.GenerateStatefulFile(image_path)
|
| - if not update_path or not stateful_update_path:
|
| - web.debug('Failed to generate update')
|
| - return False
|
| -
|
| - if move_to_static_dir:
|
| - return self.MoveImagesToStaticDir(update_path, stateful_update_path,
|
| - static_image_dir)
|
| + # Get appropriate update.gz to compare timestamps.
|
| + if self.serve_only:
|
| + cached_update_file = os.path.join(image_path, 'update.gz')
|
| else:
|
| - return True
|
| + cached_update_file = os.path.join(self.static_dir, 'update.gz')
|
|
|
| - def GenerateLatestUpdateImage(self, board_id, client_version,
|
| - static_image_dir=None):
|
| - """Generates an update using the latest image that has been built.
|
| -
|
| - This will only generate an update if the newest update is newer than that
|
| - on the client or client_version is 'ForcedUpdate'.
|
| -
|
| - Args:
|
| - board_id: Name of the board.
|
| - client_version: Current version of the client or 'ForcedUpdate'
|
| - static_image_dir: the directory to move images to after generating.
|
| - Returns:
|
| - True if the update payload was created successfully.
|
| - """
|
| - latest_image_dir = self._GetLatestImageDir(board_id)
|
| - latest_version = self._GetVersionFromDir(latest_image_dir)
|
| - latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
|
| -
|
| - web.debug('Preparing to generate update from latest built image %s.' %
|
| - latest_image_path)
|
| -
|
| - # Check to see whether or not we should update.
|
| - if client_version != 'ForcedUpdate' and not self._CanUpdate(
|
| - client_version, latest_version):
|
| - web.debug('no update')
|
| - return False
|
| -
|
| - cached_file_path = os.path.join(static_image_dir, 'update.gz')
|
| - if (os.path.exists(cached_file_path) and
|
| - not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
|
| - return True
|
| + # If the new chromiumos image is newer, re-create everything.
|
| + if (os.path.exists(cached_update_file) and
|
| + os.path.getmtime(cached_update_file) >= os.path.getmtime(bin_path)):
|
| + web.debug('Using cached update image at %s instead of %s' %
|
| + (cached_update_file, bin_path))
|
| + else:
|
| + # Unpack zip file if we are serving from a directory.
|
| + if self.serve_only and not self.UnpackZip(image_path, image_file):
|
| + web.debug('unzip image.zip failed.')
|
| + return False
|
|
|
| - return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
|
| - static_image_dir=static_image_dir)
|
| + update_file = os.path.join(image_path, 'update.gz')
|
| + web.debug('Generating update image %s' % update_file)
|
| + mkupdate_command = (
|
| + '%s/cros_generate_update_payload --image=%s --output=%s '
|
| + '--patch_kernel' % (self.scripts_dir, bin_path, update_file))
|
| + if os.system(mkupdate_command) != 0:
|
| + web.debug('Failed to create update image')
|
| + return False
|
|
|
| - def GenerateImageFromZip(self, static_image_dir):
|
| - """Generates an update from an image zip file.
|
| + # Unpack to get stateful partition.
|
| + if not self.UnpackStatefulPartition(image_path, image_file,
|
| + stateful_file):
|
| + web.debug('Failed to unpack stateful partition.')
|
| + return False
|
|
|
| - This method assumes you have an image.zip in directory you are serving
|
| - from. If this file is newer than a previously cached file, it will unzip
|
| - this file, create a payload and serve it.
|
| + mkstatefulupdate_command = 'gzip -f %s' % stateful_file
|
| + if os.system(mkstatefulupdate_command) != 0:
|
| + web.debug('Failed to create stateful update gz')
|
| + return False
|
|
|
| - Args:
|
| - static_image_dir: Directory where the zip file exists.
|
| - Returns:
|
| - True if the update payload was created successfully.
|
| - """
|
| - web.debug('Preparing to generate update from zip in %s.' % static_image_dir)
|
| - image_path = os.path.join(static_image_dir, self._GetImageName())
|
| - cached_file_path = os.path.join(static_image_dir, 'update.gz')
|
| - zip_file_path = os.path.join(static_image_dir, 'image.zip')
|
| - if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
|
| - return True
|
| + # Add gz suffix
|
| + stateful_file = '%s.gz' % stateful_file
|
| +
|
| + # Cleanup of image files.
|
| + if not self.serve_only:
|
| + try:
|
| + web.debug('Found a new image to serve, copying it to static')
|
| + shutil.copy(update_file, self.static_dir)
|
| + shutil.copy(stateful_file, self.static_dir)
|
| + os.remove(update_file)
|
| + os.remove(stateful_file)
|
| + except Exception, e:
|
| + web.debug('%s' % e)
|
| + return False
|
| + return True
|
|
|
| - if not self._UnpackZip(static_image_dir):
|
| - web.debug('unzip image.zip failed.')
|
| - return False
|
| + def GetSize(self, update_path):
|
| + return os.path.getsize(update_path)
|
|
|
| - return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
|
| - static_image_dir=None)
|
| + def GetHash(self, update_path):
|
| + cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \
|
| + % update_path
|
| + return os.popen(cmd).read().rstrip()
|
|
|
| def ImportFactoryConfigFile(self, filename, validate_checksums=False):
|
| """Imports a factory-floor server configuration file. The file should
|
| @@ -373,26 +254,24 @@ class Autoupdate(BuildObject):
|
| suffix = '_image'
|
| if key.endswith(suffix):
|
| kind = key[:-len(suffix)]
|
| - stanza[kind + '_size'] = os.path.getsize(os.path.join(
|
| - self.static_dir, stanza[kind + '_image']))
|
| + stanza[kind + '_size'] = \
|
| + os.path.getsize(self.static_dir + '/' + stanza[kind + '_image'])
|
| if validate_checksums:
|
| - factory_checksum = self._GetHash(self.static_dir + ' / ' +
|
| - stanza[kind + '_image'])
|
| + factory_checksum = self.GetHash(self.static_dir + '/' +
|
| + stanza[kind + '_image'])
|
| if factory_checksum != stanza[kind + '_checksum']:
|
| - print ('Error: checksum mismatch for %s. Expected "%s" but file '
|
| - 'has checksum "%s".' % (stanza[kind + '_image'],
|
| - stanza[kind + '_checksum'],
|
| - factory_checksum))
|
| + print 'Error: checksum mismatch for %s. Expected "%s" but file ' \
|
| + 'has checksum "%s".' % (stanza[kind + '_image'],
|
| + stanza[kind + '_checksum'],
|
| + factory_checksum)
|
| success = False
|
| -
|
| if validate_checksums:
|
| if success is False:
|
| raise Exception('Checksum mismatch in conf file.')
|
| -
|
| print 'Config file looks good.'
|
|
|
| def GetFactoryImage(self, board_id, channel):
|
| - kind = channel.rsplit(' - ', 1)[0]
|
| + kind = channel.rsplit('-', 1)[0]
|
| for stanza in self.factory_config:
|
| if board_id not in stanza['qual_ids']:
|
| continue
|
| @@ -403,84 +282,75 @@ class Autoupdate(BuildObject):
|
| stanza[kind + '_size'])
|
| return (None, None, None)
|
|
|
| - def HandleFactoryRequest(self, hostname, board_id, channel):
|
| - (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
|
| - if filename is None:
|
| - web.debug('unable to find image for board %s' % board_id)
|
| - return self.GetNoUpdatePayload()
|
| - url = 'http://%s/static/%s' % (hostname, filename)
|
| - web.debug('returning update payload ' + url)
|
| - return self.GetUpdatePayload(checksum, size, url)
|
| -
|
| def HandleUpdatePing(self, data, label=None):
|
| - """Handles an update ping from an update client.
|
| -
|
| - Args:
|
| - data: xml blob from client.
|
| - label: optional label for the update.
|
| - Returns:
|
| - Update payload message for client.
|
| - """
|
| - web.debug('handling update ping: %s' % data)
|
| + web.debug('handle update ping: %s' % data)
|
| update_dom = minidom.parseString(data)
|
| root = update_dom.firstChild
|
| -
|
| - # Check the client prefix to make sure you can support this type of update.
|
| - if (root.hasAttribute('updaterversion') and
|
| - not root.getAttribute('updaterversion').startswith(self.client_prefix)):
|
| - web.debug('Got update from unsupported updater:' +
|
| - root.getAttribute('updaterversion'))
|
| + if root.hasAttribute('updaterversion') and \
|
| + not root.getAttribute('updaterversion').startswith(
|
| + self.client_prefix):
|
| + web.debug('Got update from unsupported updater:' + \
|
| + root.getAttribute('updaterversion'))
|
| return self.GetNoUpdatePayload()
|
| -
|
| - # We only generate update payloads for updatecheck requests.
|
| - update_check = root.getElementsByTagName('o:updatecheck')
|
| - if not update_check:
|
| - web.debug('Non-update check received. Returning blank payload.')
|
| - # TODO(sosa): Generate correct non-updatecheck payload to better test
|
| - # update clients.
|
| - return self.GetNoUpdatePayload()
|
| -
|
| - # Since this is an updatecheck, get information about the requester.
|
| - hostname = web.ctx.host
|
| query = root.getElementsByTagName('o:app')[0]
|
| client_version = query.getAttribute('version')
|
| channel = query.getAttribute('track')
|
| - board_id = (query.hasAttribute('board') and query.getAttribute('board')
|
| - or self._GetDefaultBoardID())
|
| + board_id = query.hasAttribute('board') and query.getAttribute('board') \
|
| + or self.GetDefaultBoardID()
|
| + latest_image_path = self.GetLatestImagePath(board_id)
|
| + latest_version = self.GetLatestVersion(latest_image_path)
|
| + hostname = web.ctx.host
|
|
|
| - # Separate logic as Factory requests have static url's that override
|
| - # other options.
|
| + # If this is a factory floor server, return the image here:
|
| if self.factory_config:
|
| - return self.HandleFactoryRequest(hostname, board_id, channel)
|
| - else:
|
| - static_image_dir = self.static_dir
|
| - if label:
|
| - static_image_dir = os.path.join(static_image_dir, label)
|
| -
|
| - # Not for factory, find and serve the correct image given the options.
|
| - if self.forced_image:
|
| - has_built_image = self.GenerateUpdateImage(
|
| - self.forced_image, move_to_static_dir=True,
|
| - static_image_dir=static_image_dir)
|
| - # Now that we've generated it, clear out so that other pings of same
|
| - # devserver instance do not generate new images.
|
| - self.forced_image = None
|
| - elif self.serve_only:
|
| - has_built_image = self.GenerateImageFromZip(static_image_dir)
|
| - else:
|
| - has_built_image = self.GenerateLatestUpdateImage(board_id,
|
| - client_version,
|
| - static_image_dir)
|
| -
|
| - if has_built_image:
|
| - hash = self._GetHash(os.path.join(static_image_dir, 'update.gz'))
|
| - size = self._GetSize(os.path.join(static_image_dir, 'update.gz'))
|
| - if self.static_urlbase and label:
|
| - url = '%s/%s/update.gz' % (self.static_urlbase, label)
|
| - elif self.serve_only:
|
| - url = 'http://%s/static/archive/update.gz' % hostname
|
| - else:
|
| - url = 'http://%s/static/update.gz' % hostname
|
| - return self.GetUpdatePayload(hash, size, url)
|
| + (filename, checksum, size) = \
|
| + self.GetFactoryImage(board_id, channel)
|
| + if filename is None:
|
| + web.debug('unable to find image for board %s' % board_id)
|
| + return self.GetNoUpdatePayload()
|
| + url = 'http://%s/static/%s' % (hostname, filename)
|
| + web.debug('returning update payload ' + url)
|
| + return self.GetUpdatePayload(checksum, size, url)
|
| +
|
| + if client_version != 'ForcedUpdate' \
|
| + and not self.CanUpdate(client_version, latest_version):
|
| + web.debug('no update')
|
| + return self.GetNoUpdatePayload()
|
| + if label:
|
| + web.debug('Client requested version %s' % label)
|
| + # Check that matching build exists
|
| + image_path = '%s/%s' % (self.static_dir, label)
|
| + if not os.path.exists(image_path):
|
| + web.debug('%s not found.' % image_path)
|
| + return self.GetNoUpdatePayload()
|
| + # Construct a response
|
| + ok = self.BuildUpdateImage(image_path)
|
| + if ok != True:
|
| + web.debug('Failed to build an update image')
|
| + return self.GetNoUpdatePayload()
|
| + web.debug('serving update: ')
|
| + hash = self.GetHash('%s/%s/update.gz' % (self.static_dir, label))
|
| + size = self.GetSize('%s/%s/update.gz' % (self.static_dir, label))
|
| + # In case we configured images to be hosted elsewhere
|
| + # (e.g. buildbot's httpd), use that. Otherwise, serve it
|
| + # ourselves using web.py's static resource handler.
|
| + if self.static_urlbase:
|
| + urlbase = self.static_urlbase
|
| else:
|
| + urlbase = 'http://%s/static/archive/' % hostname
|
| +
|
| + url = '%s/%s/update.gz' % (urlbase, label)
|
| + return self.GetUpdatePayload(hash, size, url)
|
| + web.debug('DONE')
|
| + else:
|
| + web.debug('update found %s ' % latest_version)
|
| + ok = self.BuildUpdateImage(latest_image_path)
|
| + if ok != True:
|
| + web.debug('Failed to build an update image')
|
| return self.GetNoUpdatePayload()
|
| +
|
| + hash = self.GetHash('%s/update.gz' % self.static_dir)
|
| + size = self.GetSize('%s/update.gz' % self.static_dir)
|
| +
|
| + url = 'http://%s/static/update.gz' % hostname
|
| + return self.GetUpdatePayload(hash, size, url)
|
|
|