| Index: src/platform/dev/autoupdate.py
|
| diff --git a/src/platform/dev/autoupdate.py b/src/platform/dev/autoupdate.py
|
| index 004f86bc6382d14e8af7ac4fa055b0c4bb1ef99b..b3d4751e819469cbadf6620dfcf988bbcf4c8a81 100644
|
| --- a/src/platform/dev/autoupdate.py
|
| +++ b/src/platform/dev/autoupdate.py
|
| @@ -7,6 +7,7 @@ from xml.dom import minidom
|
|
|
| import os
|
| import shutil
|
| +import sys
|
| import web
|
|
|
| class Autoupdate(BuildObject):
|
| @@ -15,6 +16,7 @@ class Autoupdate(BuildObject):
|
| # 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, validate_factory_config=None,
|
| *args, **kwargs):
|
| super(Autoupdate, self).__init__(*args, **kwargs)
|
| self.serve_only = serve_only
|
| @@ -32,6 +34,8 @@ class Autoupdate(BuildObject):
|
| os.unlink('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 GetUpdatePayload(self, hash, size, url):
|
| payload = """<?xml version="1.0" encoding="UTF-8"?>
|
| @@ -138,24 +142,101 @@ class Autoupdate(BuildObject):
|
| def GetHash(self, update_path):
|
| cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \
|
| % update_path
|
| - web.debug(cmd)
|
| - return os.popen(cmd).read()
|
| + return os.popen(cmd).read().rstrip()
|
|
|
| + def ImportFactoryConfigFile(self, filename, validate_checksums=False):
|
| + """Imports a factory-floor server configuration file. The file should
|
| + be in this format:
|
| + config = [
|
| + {
|
| + 'qual_ids': set([1, 2, 3, "x86-generic"]),
|
| + 'factory_image': 'generic-factory.gz',
|
| + 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
|
| + 'release_image': 'generic-release.gz',
|
| + 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
|
| + 'oempartitionimg_image': 'generic-oem.gz',
|
| + 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
|
| + 'stateimg_image': 'generic-state.gz',
|
| + 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM='
|
| + },
|
| + {
|
| + 'qual_ids': set([6]),
|
| + 'factory_image': '6-factory.gz',
|
| + 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
|
| + 'release_image': '6-release.gz',
|
| + 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
|
| + 'oempartitionimg_image': '6-oem.gz',
|
| + 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
|
| + 'stateimg_image': '6-state.gz',
|
| + 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM='
|
| + },
|
| + ]
|
| + The server will look for the files by name in the static files
|
| + directory.
|
| +
|
| + If validate_checksums is True, validates checksums and exits. If
|
| + a checksum mismatch is found, it's printed to the screen.
|
| + """
|
| + f = open(filename, 'r')
|
| + output = {}
|
| + exec(f.read(), output)
|
| + self.factory_config = output['config']
|
| + success = True
|
| + for stanza in self.factory_config:
|
| + for kind in ('factory', 'oempartitionimg', 'release', 'stateimg'):
|
| + 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'])
|
| + 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)
|
| + 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]
|
| + for stanza in self.factory_config:
|
| + if board_id not in stanza['qual_ids']:
|
| + continue
|
| + return (stanza[kind + '_image'],
|
| + stanza[kind + '_checksum'],
|
| + stanza[kind + '_size'])
|
|
|
| def HandleUpdatePing(self, data, label=None):
|
| + web.debug('handle update ping')
|
| update_dom = minidom.parseString(data)
|
| root = update_dom.firstChild
|
| 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 'x86-generic'
|
| latest_image_path = self.GetLatestImagePath(board_id)
|
| latest_version = self.GetLatestVersion(latest_image_path)
|
| + hostname = web.ctx.host
|
| +
|
| + # If this is a factory floor server, return the image here:
|
| + if self.factory_config:
|
| + (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()
|
| - hostname = web.ctx.host
|
| if label:
|
| web.debug('Client requested version %s' % label)
|
| # Check that matching build exists
|
|
|