blob: 917e268d37848a9fce05c955eedb4375b7a5d7e4 [file] [log] [blame]
Chris Sosa0356d3b2010-09-16 15:46:22 -07001# Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
rtc@google.com64244662009-11-12 00:52:08 +00005from buildutil import BuildObject
rtc@google.comded22402009-10-26 22:36:21 +00006from xml.dom import minidom
7
8import os
Darin Petkov798fe7d2010-03-22 15:18:13 -07009import shutil
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070010import time
rtc@google.comded22402009-10-26 22:36:21 +000011import web
12
Chris Sosa0356d3b2010-09-16 15:46:22 -070013
rtc@google.com64244662009-11-12 00:52:08 +000014class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -070015 """Class that contains functionality that handles Chrome OS update pings.
16
17 Members:
18 serve_only: Serve images from a pre-built image.zip file. static_dir
19 must be set to the location of the image.zip.
20 factory_config: Path to the factory config file if handling factory
21 requests.
22 use_test_image: Use chromiumos_test_image.bin rather than the standard.
23 static_url_base: base URL, other than devserver, for update images.
24 client_prefix: The prefix for the update engine client.
25 forced_image: Path to an image to use for all updates.
26 """
rtc@google.comded22402009-10-26 22:36:21 +000027
Sean O'Connor1f7fd362010-04-07 16:34:52 -070028 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Chris Sosa0356d3b2010-09-16 15:46:22 -070029 factory_config_path=None, client_prefix=None, forced_image=None,
Chris Sosa5d342a22010-09-28 16:54:41 -070030 use_cached=False, *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070031 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070032 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -070033 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -070034 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -070035 if urlbase:
36 self.static_urlbase = urlbase
37 elif self.serve_only:
38 self.static_urlbase = 'http://%(host)s/static/archive'
39 else:
40 self.static_urlbase = 'http://%(host)s/static'
41
Chris Sosab63a9282010-09-02 10:43:23 -070042 self.client_prefix = client_prefix
Chris Sosa0356d3b2010-09-16 15:46:22 -070043 self.forced_image = forced_image
Chris Sosa5d342a22010-09-28 16:54:41 -070044 self.use_cached = use_cached
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070045
Chris Sosa0356d3b2010-09-16 15:46:22 -070046 def _GetSecondsSinceMidnight(self):
47 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070048 now = time.localtime()
49 return now[3] * 3600 + now[4] * 60 + now[5]
50
Chris Sosa0356d3b2010-09-16 15:46:22 -070051 def _GetDefaultBoardID(self):
52 """Returns the default board id stored in .default_board."""
53 board_file = '%s/.default_board' % (self.scripts_dir)
54 try:
55 return open(board_file).read()
56 except IOError:
57 return 'x86-generic'
58
59 def _GetLatestImageDir(self, board_id):
60 """Returns the latest image dir based on shell script."""
61 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
62 return os.popen(cmd).read().strip()
63
64 def _GetVersionFromDir(self, image_dir):
65 """Returns the version of the image based on the name of the directory."""
66 latest_version = os.path.basename(image_dir)
67 return latest_version.split('-')[0]
68
69 def _CanUpdate(self, client_version, latest_version):
70 """Returns true if the latest_version is greater than the client_version."""
71 client_tokens = client_version.replace('_', '').split('.')
72 latest_tokens = latest_version.replace('_', '').split('.')
73 web.debug('client version %s latest version %s'
74 % (client_version, latest_version))
75 for i in range(4):
76 if int(latest_tokens[i]) == int(client_tokens[i]):
77 continue
78 return int(latest_tokens[i]) > int(client_tokens[i])
79 return False
80
81 def _UnpackStatefulPartition(self, image_path, stateful_file):
82 """Given an image, unpacks its stateful partition to stateful_file."""
83 image_dir = os.path.dirname(image_path)
84 image_file = os.path.basename(image_path)
85
86 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
87 get_size = '$(cgpt show -s -i 1 %s)' % image_file
88 unpack_command = (
89 'cd %s && '
90 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
91 stateful_file, get_offset,
92 get_size))
93 web.debug(unpack_command)
94 return os.system(unpack_command) == 0
95
96 def _UnpackZip(self, image_dir):
97 """Unpacks an image.zip into a given directory."""
98 image = os.path.join(image_dir, self._GetImageName())
99 if os.path.exists(image):
100 return True
101 else:
102 # -n, never clobber an existing file, in case we get invoked
103 # simultaneously by multiple request handlers. This means that
104 # we're assuming each image.zip file lives in a versioned
105 # directory (a la Buildbot).
106 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
107
108 def _GetImageName(self):
109 """Returns the name of the image that should be used."""
110 if self.use_test_image:
111 image_name = 'chromiumos_test_image.bin'
112 else:
113 image_name = 'chromiumos_image.bin'
114 return image_name
115
116 def _IsImageNewerThanCached(self, image_path, cached_file_path):
117 """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):
Chris Sosa5d342a22010-09-28 16:54:41 -0700119 web.debug('Usable cached image found at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700120 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):
122 raise Exception('Image does not exist and cached image missing')
123 else:
124 # Only one is missing, figure out which one.
125 if os.path.exists(image_path):
126 web.debug('No cached image found - image generation required.')
127 return True
128 else:
Chris Sosa5d342a22010-09-28 16:54:41 -0700129 web.debug('Cached image found to serve at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700130 return False
131
132 def _GetSize(self, update_path):
133 """Returns the size of the file given."""
134 return os.path.getsize(update_path)
135
136 def _GetHash(self, update_path):
137 """Returns the sha1 of the file given."""
138 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
139 % update_path)
140 return os.popen(cmd).read().rstrip()
141
Darin Petkov91436cb2010-09-28 08:52:17 -0700142 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
143 # it takes advantage of reduced I/O and multiple processors. Something like:
144 # % tee < FILE > /dev/null \
145 # >( openssl dgst -sha256 -binary | openssl base64 ) \
146 # >( openssl sha1 -binary | openssl base64 )
147 def _GetSHA256(self, update_path):
148 """Returns the sha256 of the file given."""
149 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
150 update_path)
151 return os.popen(cmd).read().rstrip()
152
153 def GetUpdatePayload(self, hash, sha256, size, url):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700154 """Returns a payload to the client corresponding to a new update.
155
156 Args:
157 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700158 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700159 size: size of update blob
160 url: where to find update blob
161 Returns:
162 Xml string to be passed back to client.
163 """
rtc@google.com21a5ca32009-11-04 18:23:23 +0000164 payload = """<?xml version="1.0" encoding="UTF-8"?>
165 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700166 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000167 <app appid="{%s}" status="ok">
168 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700169 <updatecheck
170 codebase="%s"
171 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700172 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700173 needsadmin="false"
174 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000175 status="ok"/>
176 </app>
177 </gupdate>
178 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700179 return payload % (self._GetSecondsSinceMidnight(),
Darin Petkov91436cb2010-09-28 08:52:17 -0700180 self.app_id, url, hash, sha256, size)
rtc@google.comded22402009-10-26 22:36:21 +0000181
rtc@google.com21a5ca32009-11-04 18:23:23 +0000182 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700183 """Returns a payload to the client corresponding to no update."""
184 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
185 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
186 < daystart elapsed_seconds = "%s" />
187 < app appid = "{%s}" status = "ok" >
188 < ping status = "ok" />
189 < updatecheck status = "noupdate" />
190 </ app >
191 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000192 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700193 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000194
Chris Sosa0356d3b2010-09-16 15:46:22 -0700195 def GenerateUpdateFile(self, image_path):
196 """Generates an update gz given a full path to an image.
197
198 Args:
199 image_path: Full path to image.
200 Returns:
201 Path to created update_payload or None on error.
202 """
203 image_dir = os.path.dirname(image_path)
204 update_path = os.path.join(image_dir, 'update.gz')
205 web.debug('Generating update image %s' % update_path)
206
207 mkupdate_command = (
208 '%s/cros_generate_update_payload --image=%s --output=%s '
209 '--patch_kernel' % (self.scripts_dir, image_path, update_path))
210 if os.system(mkupdate_command) != 0:
211 web.debug('Failed to create base update file')
212 return None
213
214 return update_path
215
216 def GenerateStatefulFile(self, image_path):
217 """Generates a stateful update gz given a full path to an image.
218
219 Args:
220 image_path: Full path to image.
221 Returns:
222 Path to created stateful update_payload or None on error.
223 """
224 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
225
226 # Unpack to get stateful partition.
227 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
228 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
229 if os.system(mkstatefulupdate_command) == 0:
230 web.debug('Successfully generated %s.gz' % stateful_partition_path)
231 return '%s.gz' % stateful_partition_path
232
233 web.debug('Failed to create stateful update file')
234 return None
235
236 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
237 static_image_dir):
238 """Moves gz files from their directories to serving directories.
239
240 Args:
241 update_path: full path to main update gz.
242 stateful_update_path: full path to stateful partition gz.
243 static_image_dir: where to put files.
244 Returns:
245 Returns True if the files were moved over successfully.
246 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700247 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700248 shutil.copy(update_path, static_image_dir)
249 shutil.copy(stateful_update_path, static_image_dir)
250 os.remove(update_path)
251 os.remove(stateful_update_path)
252 except Exception:
253 web.debug('Failed to move %s and %s to %s' % (update_path,
254 stateful_update_path,
255 static_image_dir))
256 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700257
rtc@google.com21a5ca32009-11-04 18:23:23 +0000258 return True
rtc@google.comded22402009-10-26 22:36:21 +0000259
Chris Sosa0356d3b2010-09-16 15:46:22 -0700260 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
261 static_image_dir=None):
262 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000263
Chris Sosa0356d3b2010-09-16 15:46:22 -0700264 Args:
265 image_path: full path to the image.
266 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.
268 Returns:
269 True if the update payload was created successfully.
270 """
271 web.debug('Generating update for image %s' % image_path)
272 update_path = self.GenerateUpdateFile(image_path)
273 stateful_update_path = self.GenerateStatefulFile(image_path)
274 if not update_path or not stateful_update_path:
275 web.debug('Failed to generate update')
276 return False
277
278 if move_to_static_dir:
279 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
280 static_image_dir)
281 else:
282 return True
283
284 def GenerateLatestUpdateImage(self, board_id, client_version,
285 static_image_dir=None):
286 """Generates an update using the latest image that has been built.
287
288 This will only generate an update if the newest update is newer than that
289 on the client or client_version is 'ForcedUpdate'.
290
291 Args:
292 board_id: Name of the board.
293 client_version: Current version of the client or 'ForcedUpdate'
294 static_image_dir: the directory to move images to after generating.
295 Returns:
296 True if the update payload was created successfully.
297 """
298 latest_image_dir = self._GetLatestImageDir(board_id)
299 latest_version = self._GetVersionFromDir(latest_image_dir)
300 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
301
302 web.debug('Preparing to generate update from latest built image %s.' %
303 latest_image_path)
304
305 # Check to see whether or not we should update.
306 if client_version != 'ForcedUpdate' and not self._CanUpdate(
307 client_version, latest_version):
308 web.debug('no update')
309 return False
310
311 cached_file_path = os.path.join(static_image_dir, 'update.gz')
312 if (os.path.exists(cached_file_path) and
313 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
314 return True
315
316 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
317 static_image_dir=static_image_dir)
318
319 def GenerateImageFromZip(self, static_image_dir):
320 """Generates an update from an image zip file.
321
322 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
324 this file, create a payload and serve it.
325
326 Args:
327 static_image_dir: Directory where the zip file exists.
328 Returns:
329 True if the update payload was created successfully.
330 """
331 web.debug('Preparing to generate update from zip in %s.' % static_image_dir)
332 image_path = os.path.join(static_image_dir, self._GetImageName())
333 cached_file_path = os.path.join(static_image_dir, 'update.gz')
334 zip_file_path = os.path.join(static_image_dir, 'image.zip')
335 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
336 return True
337
338 if not self._UnpackZip(static_image_dir):
339 web.debug('unzip image.zip failed.')
340 return False
341
342 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
343 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700344
Andrew de los Reyes52620802010-04-12 13:40:07 -0700345 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
346 """Imports a factory-floor server configuration file. The file should
347 be in this format:
348 config = [
349 {
350 'qual_ids': set([1, 2, 3, "x86-generic"]),
351 'factory_image': 'generic-factory.gz',
352 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
353 'release_image': 'generic-release.gz',
354 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
355 'oempartitionimg_image': 'generic-oem.gz',
356 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700357 'efipartitionimg_image': 'generic-efi.gz',
358 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700359 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800360 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800361 'firmware_image': 'generic-firmware.gz',
362 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700363 },
364 {
365 'qual_ids': set([6]),
366 'factory_image': '6-factory.gz',
367 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
368 'release_image': '6-release.gz',
369 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
370 'oempartitionimg_image': '6-oem.gz',
371 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700372 'efipartitionimg_image': '6-efi.gz',
373 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700374 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800375 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800376 'firmware_image': '6-firmware.gz',
377 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700378 },
379 ]
380 The server will look for the files by name in the static files
381 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700382
Andrew de los Reyes52620802010-04-12 13:40:07 -0700383 If validate_checksums is True, validates checksums and exits. If
384 a checksum mismatch is found, it's printed to the screen.
385 """
386 f = open(filename, 'r')
387 output = {}
388 exec(f.read(), output)
389 self.factory_config = output['config']
390 success = True
391 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800392 for key in stanza.copy().iterkeys():
393 suffix = '_image'
394 if key.endswith(suffix):
395 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700396 stanza[kind + '_size'] = self._GetSize(os.path.join(
397 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800398 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700399 factory_checksum = self._GetHash(os.path.join(self.static_dir,
400 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800401 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700402 print ('Error: checksum mismatch for %s. Expected "%s" but file '
403 'has checksum "%s".' % (stanza[kind + '_image'],
404 stanza[kind + '_checksum'],
405 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800406 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700407
Andrew de los Reyes52620802010-04-12 13:40:07 -0700408 if validate_checksums:
409 if success is False:
410 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700411
Andrew de los Reyes52620802010-04-12 13:40:07 -0700412 print 'Config file looks good.'
413
414 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700415 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700416 for stanza in self.factory_config:
417 if board_id not in stanza['qual_ids']:
418 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700419 if kind + '_image' not in stanza:
420 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700421 return (stanza[kind + '_image'],
422 stanza[kind + '_checksum'],
423 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700424 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000425
Chris Sosa0356d3b2010-09-16 15:46:22 -0700426 def HandleFactoryRequest(self, hostname, board_id, channel):
427 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
428 if filename is None:
429 web.debug('unable to find image for board %s' % board_id)
430 return self.GetNoUpdatePayload()
431 url = 'http://%s/static/%s' % (hostname, filename)
432 web.debug('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700433 # Factory install is using memento updater which is using the sha-1 hash so
434 # setting sha-256 to an empty string.
435 return self.GetUpdatePayload(checksum, '', size, url)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700436
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700437 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700438 """Handles an update ping from an update client.
439
440 Args:
441 data: xml blob from client.
442 label: optional label for the update.
443 Returns:
444 Update payload message for client.
445 """
446 web.debug('handling update ping: %s' % data)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000447 update_dom = minidom.parseString(data)
448 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700449
Chris Sosa5d342a22010-09-28 16:54:41 -0700450 # 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
Chris Sosa0356d3b2010-09-16 15:46:22 -0700454 # Check the client prefix to make sure you can support this type of update.
455 if (root.hasAttribute('updaterversion') and
456 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
457 web.debug('Got update from unsupported updater:' +
458 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700459 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700460
461 # We only generate update payloads for updatecheck requests.
462 update_check = root.getElementsByTagName('o:updatecheck')
463 if not update_check:
464 web.debug('Non-update check received. Returning blank payload.')
465 # TODO(sosa): Generate correct non-updatecheck payload to better test
466 # update clients.
467 return self.GetNoUpdatePayload()
468
469 # Since this is an updatecheck, get information about the requester.
470 hostname = web.ctx.host
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700471 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800472 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700473 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700474 board_id = (query.hasAttribute('board') and query.getAttribute('board')
475 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700476
Chris Sosa0356d3b2010-09-16 15:46:22 -0700477 # Separate logic as Factory requests have static url's that override
478 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700479 if self.factory_config:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700480 return self.HandleFactoryRequest(hostname, board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700481 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700482 static_image_dir = self.static_dir
483 if label:
484 static_image_dir = os.path.join(static_image_dir, label)
485
Chris Sosa5d342a22010-09-28 16:54:41 -0700486 # Prefer cached image if it exists.
487 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
488 'update.gz')):
489 web.debug('Using cached image regardless of timestamps.')
490 has_built_image = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700491 else:
Chris Sosa5d342a22010-09-28 16:54:41 -0700492 if self.forced_image:
493 has_built_image = self.GenerateUpdateImage(
494 self.forced_image, move_to_static_dir=True,
495 static_image_dir=static_image_dir)
496 # Now that we've generated it, clear out so that other pings of same
497 # devserver instance do not generate new images.
498 self.forced_image = None
499 elif self.serve_only:
500 has_built_image = self.GenerateImageFromZip(static_image_dir)
501 else:
502 has_built_image = self.GenerateLatestUpdateImage(board_id,
503 client_version,
504 static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700505
506 if has_built_image:
507 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz'))
Darin Petkov91436cb2010-09-28 08:52:17 -0700508 sha256 = self._GetSHA256(os.path.join(static_image_dir, 'update.gz'))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700509 size = self._GetSize(os.path.join(static_image_dir, 'update.gz'))
Chris Sosa5d342a22010-09-28 16:54:41 -0700510 if label:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700511 url = '%s/%s/update.gz' % (self.static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700512 else:
Chris Sosa5d342a22010-09-28 16:54:41 -0700513 url = '%s/update.gz' % self.static_urlbase
514
515 web.debug('Responding to client to use url %s to get image.' % url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700516 return self.GetUpdatePayload(hash, sha256, size, url)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700517 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700518 return self.GetNoUpdatePayload()