| 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 os | 9 import os |
| 9 import shutil | 10 import shutil |
| 11 import socket |
| 10 import time | 12 import time |
| 11 import web | 13 |
| 14 def _LogMessage(message): |
| 15 cherrypy.log(message, 'UPDATE') |
| 12 | 16 |
| 13 | 17 |
| 14 class Autoupdate(BuildObject): | 18 class Autoupdate(BuildObject): |
| 15 """Class that contains functionality that handles Chrome OS update pings. | 19 """Class that contains functionality that handles Chrome OS update pings. |
| 16 | 20 |
| 17 Members: | 21 Members: |
| 18 serve_only: Serve images from a pre-built image.zip file. static_dir | 22 serve_only: Serve images from a pre-built image.zip file. static_dir |
| 19 must be set to the location of the image.zip. | 23 must be set to the location of the image.zip. |
| 20 factory_config: Path to the factory config file if handling factory | 24 factory_config: Path to the factory config file if handling factory |
| 21 requests. | 25 requests. |
| 22 use_test_image: Use chromiumos_test_image.bin rather than the standard. | 26 use_test_image: Use chromiumos_test_image.bin rather than the standard. |
| 23 static_url_base: base URL, other than devserver, for update images. | 27 static_url_base: base URL, other than devserver, for update images. |
| 24 client_prefix: The prefix for the update engine client. | 28 client_prefix: The prefix for the update engine client. |
| 25 forced_image: Path to an image to use for all updates. | 29 forced_image: Path to an image to use for all updates. |
| 26 """ | 30 """ |
| 27 | 31 |
| 28 def __init__(self, serve_only=None, test_image=False, urlbase=None, | 32 def __init__(self, serve_only=None, test_image=False, urlbase=None, |
| 29 factory_config_path=None, client_prefix=None, forced_image=None, | 33 factory_config_path=None, client_prefix=None, forced_image=None, |
| 30 use_cached=False, *args, **kwargs): | 34 use_cached=False, port=8080, *args, **kwargs): |
| 31 super(Autoupdate, self).__init__(*args, **kwargs) | 35 super(Autoupdate, self).__init__(*args, **kwargs) |
| 32 self.serve_only = serve_only | 36 self.serve_only = serve_only |
| 33 self.factory_config = factory_config_path | 37 self.factory_config = factory_config_path |
| 34 self.use_test_image = test_image | 38 self.use_test_image = test_image |
| 39 self.hostname = '%s:%s' % (socket.gethostname(), port) |
| 35 if urlbase: | 40 if urlbase: |
| 36 self.static_urlbase = urlbase | 41 self.static_urlbase = urlbase |
| 37 elif self.serve_only: | 42 elif self.serve_only: |
| 38 self.static_urlbase = 'http://%(host)s/static/archive' | 43 self.static_urlbase = 'http://%s/static/archive' % self.hostname |
| 39 else: | 44 else: |
| 40 self.static_urlbase = 'http://%(host)s/static' | 45 self.static_urlbase = 'http://%s/static' % self.hostname |
| 41 | 46 |
| 42 self.client_prefix = client_prefix | 47 self.client_prefix = client_prefix |
| 43 self.forced_image = forced_image | 48 self.forced_image = forced_image |
| 44 self.use_cached = use_cached | 49 self.use_cached = use_cached |
| 45 | 50 |
| 46 def _GetSecondsSinceMidnight(self): | 51 def _GetSecondsSinceMidnight(self): |
| 47 """Returns the seconds since midnight as a decimal value.""" | 52 """Returns the seconds since midnight as a decimal value.""" |
| 48 now = time.localtime() | 53 now = time.localtime() |
| 49 return now[3] * 3600 + now[4] * 60 + now[5] | 54 return now[3] * 3600 + now[4] * 60 + now[5] |
| 50 | 55 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 63 | 68 |
| 64 def _GetVersionFromDir(self, image_dir): | 69 def _GetVersionFromDir(self, image_dir): |
| 65 """Returns the version of the image based on the name of the directory.""" | 70 """Returns the version of the image based on the name of the directory.""" |
| 66 latest_version = os.path.basename(image_dir) | 71 latest_version = os.path.basename(image_dir) |
| 67 return latest_version.split('-')[0] | 72 return latest_version.split('-')[0] |
| 68 | 73 |
| 69 def _CanUpdate(self, client_version, latest_version): | 74 def _CanUpdate(self, client_version, latest_version): |
| 70 """Returns true if the latest_version is greater than the client_version.""" | 75 """Returns true if the latest_version is greater than the client_version.""" |
| 71 client_tokens = client_version.replace('_', '').split('.') | 76 client_tokens = client_version.replace('_', '').split('.') |
| 72 latest_tokens = latest_version.replace('_', '').split('.') | 77 latest_tokens = latest_version.replace('_', '').split('.') |
| 73 web.debug('client version %s latest version %s' | 78 _LogMessage('client version %s latest version %s' |
| 74 % (client_version, latest_version)) | 79 % (client_version, latest_version)) |
| 75 for i in range(4): | 80 for i in range(4): |
| 76 if int(latest_tokens[i]) == int(client_tokens[i]): | 81 if int(latest_tokens[i]) == int(client_tokens[i]): |
| 77 continue | 82 continue |
| 78 return int(latest_tokens[i]) > int(client_tokens[i]) | 83 return int(latest_tokens[i]) > int(client_tokens[i]) |
| 79 return False | 84 return False |
| 80 | 85 |
| 81 def _UnpackStatefulPartition(self, image_path, stateful_file): | 86 def _UnpackStatefulPartition(self, image_path, stateful_file): |
| 82 """Given an image, unpacks its stateful partition to stateful_file.""" | 87 """Given an image, unpacks its stateful partition to stateful_file.""" |
| 83 image_dir = os.path.dirname(image_path) | 88 image_dir = os.path.dirname(image_path) |
| 84 image_file = os.path.basename(image_path) | 89 image_file = os.path.basename(image_path) |
| 85 | 90 |
| 86 get_offset = '$(cgpt show -b -i 1 %s)' % image_file | 91 get_offset = '$(cgpt show -b -i 1 %s)' % image_file |
| 87 get_size = '$(cgpt show -s -i 1 %s)' % image_file | 92 get_size = '$(cgpt show -s -i 1 %s)' % image_file |
| 88 unpack_command = ( | 93 unpack_command = ( |
| 89 'cd %s && ' | 94 'cd %s && ' |
| 90 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file, | 95 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file, |
| 91 stateful_file, get_offset, | 96 stateful_file, get_offset, |
| 92 get_size)) | 97 get_size)) |
| 93 web.debug(unpack_command) | 98 _LogMessage(unpack_command) |
| 94 return os.system(unpack_command) == 0 | 99 return os.system(unpack_command) == 0 |
| 95 | 100 |
| 96 def _UnpackZip(self, image_dir): | 101 def _UnpackZip(self, image_dir): |
| 97 """Unpacks an image.zip into a given directory.""" | 102 """Unpacks an image.zip into a given directory.""" |
| 98 image = os.path.join(image_dir, self._GetImageName()) | 103 image = os.path.join(image_dir, self._GetImageName()) |
| 99 if os.path.exists(image): | 104 if os.path.exists(image): |
| 100 return True | 105 return True |
| 101 else: | 106 else: |
| 102 # -n, never clobber an existing file, in case we get invoked | 107 # -n, never clobber an existing file, in case we get invoked |
| 103 # simultaneously by multiple request handlers. This means that | 108 # simultaneously by multiple request handlers. This means that |
| 104 # we're assuming each image.zip file lives in a versioned | 109 # we're assuming each image.zip file lives in a versioned |
| 105 # directory (a la Buildbot). | 110 # directory (a la Buildbot). |
| 106 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0 | 111 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0 |
| 107 | 112 |
| 108 def _GetImageName(self): | 113 def _GetImageName(self): |
| 109 """Returns the name of the image that should be used.""" | 114 """Returns the name of the image that should be used.""" |
| 110 if self.use_test_image: | 115 if self.use_test_image: |
| 111 image_name = 'chromiumos_test_image.bin' | 116 image_name = 'chromiumos_test_image.bin' |
| 112 else: | 117 else: |
| 113 image_name = 'chromiumos_image.bin' | 118 image_name = 'chromiumos_image.bin' |
| 114 return image_name | 119 return image_name |
| 115 | 120 |
| 116 def _IsImageNewerThanCached(self, image_path, cached_file_path): | 121 def _IsImageNewerThanCached(self, image_path, cached_file_path): |
| 117 """Returns true if the image is newer than the cached image.""" | 122 """Returns true if the image is newer than the cached image.""" |
| 118 if os.path.exists(cached_file_path) and os.path.exists(image_path): | 123 if os.path.exists(cached_file_path) and os.path.exists(image_path): |
| 119 web.debug('Usable cached image found at %s.' % cached_file_path) | 124 _LogMessage('Usable cached image found at %s.' % cached_file_path) |
| 120 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path) | 125 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path) |
| 121 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path)
: | 126 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path)
: |
| 122 raise Exception('Image does not exist and cached image missing') | 127 raise Exception('Image does not exist and cached image missing') |
| 123 else: | 128 else: |
| 124 # Only one is missing, figure out which one. | 129 # Only one is missing, figure out which one. |
| 125 if os.path.exists(image_path): | 130 if os.path.exists(image_path): |
| 126 web.debug('No cached image found - image generation required.') | 131 _LogMessage('No cached image found - image generation required.') |
| 127 return True | 132 return True |
| 128 else: | 133 else: |
| 129 web.debug('Cached image found to serve at %s.' % cached_file_path) | 134 _LogMessage('Cached image found to serve at %s.' % cached_file_path) |
| 130 return False | 135 return False |
| 131 | 136 |
| 132 def _GetSize(self, update_path): | 137 def _GetSize(self, update_path): |
| 133 """Returns the size of the file given.""" | 138 """Returns the size of the file given.""" |
| 134 return os.path.getsize(update_path) | 139 return os.path.getsize(update_path) |
| 135 | 140 |
| 136 def _GetHash(self, update_path): | 141 def _GetHash(self, update_path): |
| 137 """Returns the sha1 of the file given.""" | 142 """Returns the sha1 of the file given.""" |
| 138 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';' | 143 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';' |
| 139 % update_path) | 144 % update_path) |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 195 def GenerateUpdateFile(self, image_path): | 200 def GenerateUpdateFile(self, image_path): |
| 196 """Generates an update gz given a full path to an image. | 201 """Generates an update gz given a full path to an image. |
| 197 | 202 |
| 198 Args: | 203 Args: |
| 199 image_path: Full path to image. | 204 image_path: Full path to image. |
| 200 Returns: | 205 Returns: |
| 201 Path to created update_payload or None on error. | 206 Path to created update_payload or None on error. |
| 202 """ | 207 """ |
| 203 image_dir = os.path.dirname(image_path) | 208 image_dir = os.path.dirname(image_path) |
| 204 update_path = os.path.join(image_dir, 'update.gz') | 209 update_path = os.path.join(image_dir, 'update.gz') |
| 205 web.debug('Generating update image %s' % update_path) | 210 _LogMessage('Generating update image %s' % update_path) |
| 206 | 211 |
| 207 mkupdate_command = ( | 212 mkupdate_command = ( |
| 208 '%s/cros_generate_update_payload --image=%s --output=%s ' | 213 '%s/cros_generate_update_payload --image=%s --output=%s ' |
| 209 '--patch_kernel' % (self.scripts_dir, image_path, update_path)) | 214 '--patch_kernel' % (self.scripts_dir, image_path, update_path)) |
| 210 if os.system(mkupdate_command) != 0: | 215 if os.system(mkupdate_command) != 0: |
| 211 web.debug('Failed to create base update file') | 216 _LogMessage('Failed to create base update file') |
| 212 return None | 217 return None |
| 213 | 218 |
| 214 return update_path | 219 return update_path |
| 215 | 220 |
| 216 def GenerateStatefulFile(self, image_path): | 221 def GenerateStatefulFile(self, image_path): |
| 217 """Generates a stateful update gz given a full path to an image. | 222 """Generates a stateful update gz given a full path to an image. |
| 218 | 223 |
| 219 Args: | 224 Args: |
| 220 image_path: Full path to image. | 225 image_path: Full path to image. |
| 221 Returns: | 226 Returns: |
| 222 Path to created stateful update_payload or None on error. | 227 Path to created stateful update_payload or None on error. |
| 223 """ | 228 """ |
| 224 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path) | 229 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path) |
| 225 | 230 |
| 226 # Unpack to get stateful partition. | 231 # Unpack to get stateful partition. |
| 227 if self._UnpackStatefulPartition(image_path, stateful_partition_path): | 232 if self._UnpackStatefulPartition(image_path, stateful_partition_path): |
| 228 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path | 233 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path |
| 229 if os.system(mkstatefulupdate_command) == 0: | 234 if os.system(mkstatefulupdate_command) == 0: |
| 230 web.debug('Successfully generated %s.gz' % stateful_partition_path) | 235 _LogMessage('Successfully generated %s.gz' % stateful_partition_path) |
| 231 return '%s.gz' % stateful_partition_path | 236 return '%s.gz' % stateful_partition_path |
| 232 | 237 |
| 233 web.debug('Failed to create stateful update file') | 238 _LogMessage('Failed to create stateful update file') |
| 234 return None | 239 return None |
| 235 | 240 |
| 236 def MoveImagesToStaticDir(self, update_path, stateful_update_path, | 241 def MoveImagesToStaticDir(self, update_path, stateful_update_path, |
| 237 static_image_dir): | 242 static_image_dir): |
| 238 """Moves gz files from their directories to serving directories. | 243 """Moves gz files from their directories to serving directories. |
| 239 | 244 |
| 240 Args: | 245 Args: |
| 241 update_path: full path to main update gz. | 246 update_path: full path to main update gz. |
| 242 stateful_update_path: full path to stateful partition gz. | 247 stateful_update_path: full path to stateful partition gz. |
| 243 static_image_dir: where to put files. | 248 static_image_dir: where to put files. |
| 244 Returns: | 249 Returns: |
| 245 Returns True if the files were moved over successfully. | 250 Returns True if the files were moved over successfully. |
| 246 """ | 251 """ |
| 247 try: | 252 try: |
| 248 shutil.copy(update_path, static_image_dir) | 253 shutil.copy(update_path, static_image_dir) |
| 249 shutil.copy(stateful_update_path, static_image_dir) | 254 shutil.copy(stateful_update_path, static_image_dir) |
| 250 os.remove(update_path) | 255 os.remove(update_path) |
| 251 os.remove(stateful_update_path) | 256 os.remove(stateful_update_path) |
| 252 except Exception: | 257 except Exception: |
| 253 web.debug('Failed to move %s and %s to %s' % (update_path, | 258 _LogMessage('Failed to move %s and %s to %s' % (update_path, |
| 254 stateful_update_path, | 259 stateful_update_path, |
| 255 static_image_dir)) | 260 static_image_dir)) |
| 256 return False | 261 return False |
| 257 | 262 |
| 258 return True | 263 return True |
| 259 | 264 |
| 260 def GenerateUpdateImage(self, image_path, move_to_static_dir=False, | 265 def GenerateUpdateImage(self, image_path, move_to_static_dir=False, |
| 261 static_image_dir=None): | 266 static_image_dir=None): |
| 262 """Force generates an update payload based on the given image_path. | 267 """Force generates an update payload based on the given image_path. |
| 263 | 268 |
| 264 Args: | 269 Args: |
| 265 image_path: full path to the image. | 270 image_path: full path to the image. |
| 266 move_to_static_dir: Moves the files from their dir to the static dir. | 271 move_to_static_dir: Moves the files from their dir to the static dir. |
| 267 static_image_dir: the directory to move images to after generating. | 272 static_image_dir: the directory to move images to after generating. |
| 268 Returns: | 273 Returns: |
| 269 True if the update payload was created successfully. | 274 True if the update payload was created successfully. |
| 270 """ | 275 """ |
| 271 web.debug('Generating update for image %s' % image_path) | 276 _LogMessage('Generating update for image %s' % image_path) |
| 272 update_path = self.GenerateUpdateFile(image_path) | 277 update_path = self.GenerateUpdateFile(image_path) |
| 273 stateful_update_path = self.GenerateStatefulFile(image_path) | 278 stateful_update_path = self.GenerateStatefulFile(image_path) |
| 274 if not update_path or not stateful_update_path: | 279 if not update_path or not stateful_update_path: |
| 275 web.debug('Failed to generate update') | 280 _LogMessage('Failed to generate update') |
| 276 return False | 281 return False |
| 277 | 282 |
| 278 if move_to_static_dir: | 283 if move_to_static_dir: |
| 279 return self.MoveImagesToStaticDir(update_path, stateful_update_path, | 284 return self.MoveImagesToStaticDir(update_path, stateful_update_path, |
| 280 static_image_dir) | 285 static_image_dir) |
| 281 else: | 286 else: |
| 282 return True | 287 return True |
| 283 | 288 |
| 284 def GenerateLatestUpdateImage(self, board_id, client_version, | 289 def GenerateLatestUpdateImage(self, board_id, client_version, |
| 285 static_image_dir=None): | 290 static_image_dir=None): |
| 286 """Generates an update using the latest image that has been built. | 291 """Generates an update using the latest image that has been built. |
| 287 | 292 |
| 288 This will only generate an update if the newest update is newer than that | 293 This will only generate an update if the newest update is newer than that |
| 289 on the client or client_version is 'ForcedUpdate'. | 294 on the client or client_version is 'ForcedUpdate'. |
| 290 | 295 |
| 291 Args: | 296 Args: |
| 292 board_id: Name of the board. | 297 board_id: Name of the board. |
| 293 client_version: Current version of the client or 'ForcedUpdate' | 298 client_version: Current version of the client or 'ForcedUpdate' |
| 294 static_image_dir: the directory to move images to after generating. | 299 static_image_dir: the directory to move images to after generating. |
| 295 Returns: | 300 Returns: |
| 296 True if the update payload was created successfully. | 301 True if the update payload was created successfully. |
| 297 """ | 302 """ |
| 298 latest_image_dir = self._GetLatestImageDir(board_id) | 303 latest_image_dir = self._GetLatestImageDir(board_id) |
| 299 latest_version = self._GetVersionFromDir(latest_image_dir) | 304 latest_version = self._GetVersionFromDir(latest_image_dir) |
| 300 latest_image_path = os.path.join(latest_image_dir, self._GetImageName()) | 305 latest_image_path = os.path.join(latest_image_dir, self._GetImageName()) |
| 301 | 306 |
| 302 web.debug('Preparing to generate update from latest built image %s.' % | 307 _LogMessage('Preparing to generate update from latest built image %s.' % |
| 303 latest_image_path) | 308 latest_image_path) |
| 304 | 309 |
| 305 # Check to see whether or not we should update. | 310 # Check to see whether or not we should update. |
| 306 if client_version != 'ForcedUpdate' and not self._CanUpdate( | 311 if client_version != 'ForcedUpdate' and not self._CanUpdate( |
| 307 client_version, latest_version): | 312 client_version, latest_version): |
| 308 web.debug('no update') | 313 _LogMessage('no update') |
| 309 return False | 314 return False |
| 310 | 315 |
| 311 cached_file_path = os.path.join(static_image_dir, 'update.gz') | 316 cached_file_path = os.path.join(static_image_dir, 'update.gz') |
| 312 if (os.path.exists(cached_file_path) and | 317 if (os.path.exists(cached_file_path) and |
| 313 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)): | 318 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)): |
| 314 return True | 319 return True |
| 315 | 320 |
| 316 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True, | 321 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True, |
| 317 static_image_dir=static_image_dir) | 322 static_image_dir=static_image_dir) |
| 318 | 323 |
| 319 def GenerateImageFromZip(self, static_image_dir): | 324 def GenerateImageFromZip(self, static_image_dir): |
| 320 """Generates an update from an image zip file. | 325 """Generates an update from an image zip file. |
| 321 | 326 |
| 322 This method assumes you have an image.zip in directory you are serving | 327 This method assumes you have an image.zip in directory you are serving |
| 323 from. If this file is newer than a previously cached file, it will unzip | 328 from. If this file is newer than a previously cached file, it will unzip |
| 324 this file, create a payload and serve it. | 329 this file, create a payload and serve it. |
| 325 | 330 |
| 326 Args: | 331 Args: |
| 327 static_image_dir: Directory where the zip file exists. | 332 static_image_dir: Directory where the zip file exists. |
| 328 Returns: | 333 Returns: |
| 329 True if the update payload was created successfully. | 334 True if the update payload was created successfully. |
| 330 """ | 335 """ |
| 331 web.debug('Preparing to generate update from zip in %s.' % static_image_dir) | 336 _LogMessage('Preparing to generate update from zip in %s.' % static_image_di
r) |
| 332 image_path = os.path.join(static_image_dir, self._GetImageName()) | 337 image_path = os.path.join(static_image_dir, self._GetImageName()) |
| 333 cached_file_path = os.path.join(static_image_dir, 'update.gz') | 338 cached_file_path = os.path.join(static_image_dir, 'update.gz') |
| 334 zip_file_path = os.path.join(static_image_dir, 'image.zip') | 339 zip_file_path = os.path.join(static_image_dir, 'image.zip') |
| 335 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path): | 340 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path): |
| 336 return True | 341 return True |
| 337 | 342 |
| 338 if not self._UnpackZip(static_image_dir): | 343 if not self._UnpackZip(static_image_dir): |
| 339 web.debug('unzip image.zip failed.') | 344 _LogMessage('unzip image.zip failed.') |
| 340 return False | 345 return False |
| 341 | 346 |
| 342 return self.GenerateUpdateImage(image_path, move_to_static_dir=False, | 347 return self.GenerateUpdateImage(image_path, move_to_static_dir=False, |
| 343 static_image_dir=None) | 348 static_image_dir=None) |
| 344 | 349 |
| 345 def ImportFactoryConfigFile(self, filename, validate_checksums=False): | 350 def ImportFactoryConfigFile(self, filename, validate_checksums=False): |
| 346 """Imports a factory-floor server configuration file. The file should | 351 """Imports a factory-floor server configuration file. The file should |
| 347 be in this format: | 352 be in this format: |
| 348 config = [ | 353 config = [ |
| 349 { | 354 { |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 416 for stanza in self.factory_config: | 421 for stanza in self.factory_config: |
| 417 if board_id not in stanza['qual_ids']: | 422 if board_id not in stanza['qual_ids']: |
| 418 continue | 423 continue |
| 419 if kind + '_image' not in stanza: | 424 if kind + '_image' not in stanza: |
| 420 break | 425 break |
| 421 return (stanza[kind + '_image'], | 426 return (stanza[kind + '_image'], |
| 422 stanza[kind + '_checksum'], | 427 stanza[kind + '_checksum'], |
| 423 stanza[kind + '_size']) | 428 stanza[kind + '_size']) |
| 424 return (None, None, None) | 429 return (None, None, None) |
| 425 | 430 |
| 426 def HandleFactoryRequest(self, hostname, board_id, channel): | 431 def HandleFactoryRequest(self, board_id, channel): |
| 427 (filename, checksum, size) = self.GetFactoryImage(board_id, channel) | 432 (filename, checksum, size) = self.GetFactoryImage(board_id, channel) |
| 428 if filename is None: | 433 if filename is None: |
| 429 web.debug('unable to find image for board %s' % board_id) | 434 _LogMessage('unable to find image for board %s' % board_id) |
| 430 return self.GetNoUpdatePayload() | 435 return self.GetNoUpdatePayload() |
| 431 url = 'http://%s/static/%s' % (hostname, filename) | 436 url = 'http://%s/static/%s' % (self.hostname, filename) |
| 432 web.debug('returning update payload ' + url) | 437 _LogMessage('returning update payload ' + url) |
| 433 # Factory install is using memento updater which is using the sha-1 hash so | 438 # Factory install is using memento updater which is using the sha-1 hash so |
| 434 # setting sha-256 to an empty string. | 439 # setting sha-256 to an empty string. |
| 435 return self.GetUpdatePayload(checksum, '', size, url) | 440 return self.GetUpdatePayload(checksum, '', size, url) |
| 436 | 441 |
| 437 def HandleUpdatePing(self, data, label=None): | 442 def HandleUpdatePing(self, data, label=None): |
| 438 """Handles an update ping from an update client. | 443 """Handles an update ping from an update client. |
| 439 | 444 |
| 440 Args: | 445 Args: |
| 441 data: xml blob from client. | 446 data: xml blob from client. |
| 442 label: optional label for the update. | 447 label: optional label for the update. |
| 443 Returns: | 448 Returns: |
| 444 Update payload message for client. | 449 Update payload message for client. |
| 445 """ | 450 """ |
| 446 web.debug('handling update ping: %s' % data) | 451 _LogMessage('handling update ping: %s' % data) |
| 447 update_dom = minidom.parseString(data) | 452 update_dom = minidom.parseString(data) |
| 448 root = update_dom.firstChild | 453 root = update_dom.firstChild |
| 449 | 454 |
| 450 # Parse host if not done yet. | |
| 451 if '%(host)' in self.static_urlbase: | |
| 452 self.static_urlbase = self.static_urlbase % {'host' : web.ctx.host} | |
| 453 | |
| 454 # Check the client prefix to make sure you can support this type of update. | 455 # Check the client prefix to make sure you can support this type of update. |
| 455 if (root.hasAttribute('updaterversion') and | 456 if (root.hasAttribute('updaterversion') and |
| 456 not root.getAttribute('updaterversion').startswith(self.client_prefix)): | 457 not root.getAttribute('updaterversion').startswith(self.client_prefix)): |
| 457 web.debug('Got update from unsupported updater:' + | 458 _LogMessage('Got update from unsupported updater:' + |
| 458 root.getAttribute('updaterversion')) | 459 root.getAttribute('updaterversion')) |
| 459 return self.GetNoUpdatePayload() | 460 return self.GetNoUpdatePayload() |
| 460 | 461 |
| 461 # We only generate update payloads for updatecheck requests. | 462 # We only generate update payloads for updatecheck requests. |
| 462 update_check = root.getElementsByTagName('o:updatecheck') | 463 update_check = root.getElementsByTagName('o:updatecheck') |
| 463 if not update_check: | 464 if not update_check: |
| 464 web.debug('Non-update check received. Returning blank payload.') | 465 _LogMessage('Non-update check received. Returning blank payload.') |
| 465 # TODO(sosa): Generate correct non-updatecheck payload to better test | 466 # TODO(sosa): Generate correct non-updatecheck payload to better test |
| 466 # update clients. | 467 # update clients. |
| 467 return self.GetNoUpdatePayload() | 468 return self.GetNoUpdatePayload() |
| 468 | 469 |
| 469 # Since this is an updatecheck, get information about the requester. | 470 # Since this is an updatecheck, get information about the requester. |
| 470 hostname = web.ctx.host | |
| 471 query = root.getElementsByTagName('o:app')[0] | 471 query = root.getElementsByTagName('o:app')[0] |
| 472 client_version = query.getAttribute('version') | 472 client_version = query.getAttribute('version') |
| 473 channel = query.getAttribute('track') | 473 channel = query.getAttribute('track') |
| 474 board_id = (query.hasAttribute('board') and query.getAttribute('board') | 474 board_id = (query.hasAttribute('board') and query.getAttribute('board') |
| 475 or self._GetDefaultBoardID()) | 475 or self._GetDefaultBoardID()) |
| 476 | 476 |
| 477 # Separate logic as Factory requests have static url's that override | 477 # Separate logic as Factory requests have static url's that override |
| 478 # other options. | 478 # other options. |
| 479 if self.factory_config: | 479 if self.factory_config: |
| 480 return self.HandleFactoryRequest(hostname, board_id, channel) | 480 return self.HandleFactoryRequest(board_id, channel) |
| 481 else: | 481 else: |
| 482 static_image_dir = self.static_dir | 482 static_image_dir = self.static_dir |
| 483 if label: | 483 if label: |
| 484 static_image_dir = os.path.join(static_image_dir, label) | 484 static_image_dir = os.path.join(static_image_dir, label) |
| 485 | 485 |
| 486 # Prefer cached image if it exists. | 486 # Prefer cached image if it exists. |
| 487 if self.use_cached and os.path.exists(os.path.join(static_image_dir, | 487 if self.use_cached and os.path.exists(os.path.join(static_image_dir, |
| 488 'update.gz')): | 488 'update.gz')): |
| 489 web.debug('Using cached image regardless of timestamps.') | 489 _LogMessage('Using cached image regardless of timestamps.') |
| 490 has_built_image = True | 490 has_built_image = True |
| 491 else: | 491 else: |
| 492 if self.forced_image: | 492 if self.forced_image: |
| 493 has_built_image = self.GenerateUpdateImage( | 493 has_built_image = self.GenerateUpdateImage( |
| 494 self.forced_image, move_to_static_dir=True, | 494 self.forced_image, move_to_static_dir=True, |
| 495 static_image_dir=static_image_dir) | 495 static_image_dir=static_image_dir) |
| 496 # Now that we've generated it, clear out so that other pings of same | 496 # Now that we've generated it, clear out so that other pings of same |
| 497 # devserver instance do not generate new images. | 497 # devserver instance do not generate new images. |
| 498 self.forced_image = None | 498 self.forced_image = None |
| 499 elif self.serve_only: | 499 elif self.serve_only: |
| 500 has_built_image = self.GenerateImageFromZip(static_image_dir) | 500 has_built_image = self.GenerateImageFromZip(static_image_dir) |
| 501 else: | 501 else: |
| 502 has_built_image = self.GenerateLatestUpdateImage(board_id, | 502 has_built_image = self.GenerateLatestUpdateImage(board_id, |
| 503 client_version, | 503 client_version, |
| 504 static_image_dir) | 504 static_image_dir) |
| 505 | 505 |
| 506 if has_built_image: | 506 if has_built_image: |
| 507 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz')) | 507 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz')) |
| 508 sha256 = self._GetSHA256(os.path.join(static_image_dir, 'update.gz')) | 508 sha256 = self._GetSHA256(os.path.join(static_image_dir, 'update.gz')) |
| 509 size = self._GetSize(os.path.join(static_image_dir, 'update.gz')) | 509 size = self._GetSize(os.path.join(static_image_dir, 'update.gz')) |
| 510 if label: | 510 if label: |
| 511 url = '%s/%s/update.gz' % (self.static_urlbase, label) | 511 url = '%s/%s/update.gz' % (self.static_urlbase, label) |
| 512 else: | 512 else: |
| 513 url = '%s/update.gz' % self.static_urlbase | 513 url = '%s/update.gz' % self.static_urlbase |
| 514 | 514 |
| 515 web.debug('Responding to client to use url %s to get image.' % url) | 515 _LogMessage('Responding to client to use url %s to get image.' % url) |
| 516 return self.GetUpdatePayload(hash, sha256, size, url) | 516 return self.GetUpdatePayload(hash, sha256, size, url) |
| 517 else: | 517 else: |
| 518 return self.GetNoUpdatePayload() | 518 return self.GetNoUpdatePayload() |
| OLD | NEW |