blob: b432b91beef8d25494b0ab51df96755f0d3f38aa [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
Chris Sosa7c931362010-10-11 19:49:01 -07008import cherrypy
rtc@google.comded22402009-10-26 22:36:21 +00009import os
Darin Petkov798fe7d2010-03-22 15:18:13 -070010import shutil
Chris Sosa7c931362010-10-11 19:49:01 -070011import socket
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070012import time
Chris Sosa7c931362010-10-11 19:49:01 -070013
14def _LogMessage(message):
15 cherrypy.log(message, 'UPDATE')
rtc@google.comded22402009-10-26 22:36:21 +000016
Chris Sosa0356d3b2010-09-16 15:46:22 -070017
rtc@google.com64244662009-11-12 00:52:08 +000018class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -070019 """Class that contains functionality that handles Chrome OS update pings.
20
21 Members:
22 serve_only: Serve images from a pre-built image.zip file. static_dir
23 must be set to the location of the image.zip.
24 factory_config: Path to the factory config file if handling factory
25 requests.
26 use_test_image: Use chromiumos_test_image.bin rather than the standard.
27 static_url_base: base URL, other than devserver, for update images.
28 client_prefix: The prefix for the update engine client.
29 forced_image: Path to an image to use for all updates.
30 """
rtc@google.comded22402009-10-26 22:36:21 +000031
Sean O'Connor1f7fd362010-04-07 16:34:52 -070032 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Chris Sosa0356d3b2010-09-16 15:46:22 -070033 factory_config_path=None, client_prefix=None, forced_image=None,
Chris Sosa7c931362010-10-11 19:49:01 -070034 use_cached=False, port=8080, *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070035 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070036 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -070037 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -070038 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -070039 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -070040 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -070041 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -070042 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -070043
Chris Sosab63a9282010-09-02 10:43:23 -070044 self.client_prefix = client_prefix
Chris Sosa0356d3b2010-09-16 15:46:22 -070045 self.forced_image = forced_image
Chris Sosa5d342a22010-09-28 16:54:41 -070046 self.use_cached = use_cached
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070047
Chris Sosa0356d3b2010-09-16 15:46:22 -070048 def _GetSecondsSinceMidnight(self):
49 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070050 now = time.localtime()
51 return now[3] * 3600 + now[4] * 60 + now[5]
52
Chris Sosa0356d3b2010-09-16 15:46:22 -070053 def _GetDefaultBoardID(self):
54 """Returns the default board id stored in .default_board."""
55 board_file = '%s/.default_board' % (self.scripts_dir)
56 try:
57 return open(board_file).read()
58 except IOError:
59 return 'x86-generic'
60
61 def _GetLatestImageDir(self, board_id):
62 """Returns the latest image dir based on shell script."""
63 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
64 return os.popen(cmd).read().strip()
65
66 def _GetVersionFromDir(self, image_dir):
67 """Returns the version of the image based on the name of the directory."""
68 latest_version = os.path.basename(image_dir)
69 return latest_version.split('-')[0]
70
71 def _CanUpdate(self, client_version, latest_version):
72 """Returns true if the latest_version is greater than the client_version."""
73 client_tokens = client_version.replace('_', '').split('.')
74 latest_tokens = latest_version.replace('_', '').split('.')
Chris Sosa7c931362010-10-11 19:49:01 -070075 _LogMessage('client version %s latest version %s'
Chris Sosa0356d3b2010-09-16 15:46:22 -070076 % (client_version, latest_version))
77 for i in range(4):
78 if int(latest_tokens[i]) == int(client_tokens[i]):
79 continue
80 return int(latest_tokens[i]) > int(client_tokens[i])
81 return False
82
83 def _UnpackStatefulPartition(self, image_path, stateful_file):
84 """Given an image, unpacks its stateful partition to stateful_file."""
85 image_dir = os.path.dirname(image_path)
86 image_file = os.path.basename(image_path)
87
88 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
89 get_size = '$(cgpt show -s -i 1 %s)' % image_file
90 unpack_command = (
91 'cd %s && '
92 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
93 stateful_file, get_offset,
94 get_size))
Chris Sosa7c931362010-10-11 19:49:01 -070095 _LogMessage(unpack_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -070096 return os.system(unpack_command) == 0
97
98 def _UnpackZip(self, image_dir):
99 """Unpacks an image.zip into a given directory."""
100 image = os.path.join(image_dir, self._GetImageName())
101 if os.path.exists(image):
102 return True
103 else:
104 # -n, never clobber an existing file, in case we get invoked
105 # simultaneously by multiple request handlers. This means that
106 # we're assuming each image.zip file lives in a versioned
107 # directory (a la Buildbot).
108 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
109
110 def _GetImageName(self):
111 """Returns the name of the image that should be used."""
112 if self.use_test_image:
113 image_name = 'chromiumos_test_image.bin'
114 else:
115 image_name = 'chromiumos_image.bin'
116 return image_name
117
118 def _IsImageNewerThanCached(self, image_path, cached_file_path):
119 """Returns true if the image is newer than the cached image."""
120 if os.path.exists(cached_file_path) and os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700121 _LogMessage('Usable cached image found at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700122 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
123 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
124 raise Exception('Image does not exist and cached image missing')
125 else:
126 # Only one is missing, figure out which one.
127 if os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700128 _LogMessage('No cached image found - image generation required.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700129 return True
130 else:
Chris Sosa7c931362010-10-11 19:49:01 -0700131 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700132 return False
133
134 def _GetSize(self, update_path):
135 """Returns the size of the file given."""
136 return os.path.getsize(update_path)
137
138 def _GetHash(self, update_path):
139 """Returns the sha1 of the file given."""
140 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
141 % update_path)
142 return os.popen(cmd).read().rstrip()
143
Darin Petkov91436cb2010-09-28 08:52:17 -0700144 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
145 # it takes advantage of reduced I/O and multiple processors. Something like:
146 # % tee < FILE > /dev/null \
147 # >( openssl dgst -sha256 -binary | openssl base64 ) \
148 # >( openssl sha1 -binary | openssl base64 )
149 def _GetSHA256(self, update_path):
150 """Returns the sha256 of the file given."""
151 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
152 update_path)
153 return os.popen(cmd).read().rstrip()
154
155 def GetUpdatePayload(self, hash, sha256, size, url):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700156 """Returns a payload to the client corresponding to a new update.
157
158 Args:
159 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700160 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700161 size: size of update blob
162 url: where to find update blob
163 Returns:
164 Xml string to be passed back to client.
165 """
rtc@google.com21a5ca32009-11-04 18:23:23 +0000166 payload = """<?xml version="1.0" encoding="UTF-8"?>
167 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700168 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000169 <app appid="{%s}" status="ok">
170 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700171 <updatecheck
172 codebase="%s"
173 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700174 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700175 needsadmin="false"
176 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000177 status="ok"/>
178 </app>
179 </gupdate>
180 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700181 return payload % (self._GetSecondsSinceMidnight(),
Darin Petkov91436cb2010-09-28 08:52:17 -0700182 self.app_id, url, hash, sha256, size)
rtc@google.comded22402009-10-26 22:36:21 +0000183
rtc@google.com21a5ca32009-11-04 18:23:23 +0000184 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700185 """Returns a payload to the client corresponding to no update."""
186 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
187 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
188 < daystart elapsed_seconds = "%s" />
189 < app appid = "{%s}" status = "ok" >
190 < ping status = "ok" />
191 < updatecheck status = "noupdate" />
192 </ app >
193 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000194 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700195 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000196
Chris Sosa0356d3b2010-09-16 15:46:22 -0700197 def GenerateUpdateFile(self, image_path):
198 """Generates an update gz given a full path to an image.
199
200 Args:
201 image_path: Full path to image.
202 Returns:
203 Path to created update_payload or None on error.
204 """
205 image_dir = os.path.dirname(image_path)
206 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa7c931362010-10-11 19:49:01 -0700207 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700208
209 mkupdate_command = (
210 '%s/cros_generate_update_payload --image=%s --output=%s '
211 '--patch_kernel' % (self.scripts_dir, image_path, update_path))
212 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700213 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700214 return None
215
216 return update_path
217
218 def GenerateStatefulFile(self, image_path):
219 """Generates a stateful update gz given a full path to an image.
220
221 Args:
222 image_path: Full path to image.
223 Returns:
224 Path to created stateful update_payload or None on error.
225 """
226 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
227
228 # Unpack to get stateful partition.
229 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
230 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
231 if os.system(mkstatefulupdate_command) == 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700232 _LogMessage('Successfully generated %s.gz' % stateful_partition_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700233 return '%s.gz' % stateful_partition_path
234
Chris Sosa7c931362010-10-11 19:49:01 -0700235 _LogMessage('Failed to create stateful update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700236 return None
237
238 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
239 static_image_dir):
240 """Moves gz files from their directories to serving directories.
241
242 Args:
243 update_path: full path to main update gz.
244 stateful_update_path: full path to stateful partition gz.
245 static_image_dir: where to put files.
246 Returns:
247 Returns True if the files were moved over successfully.
248 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700249 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700250 shutil.copy(update_path, static_image_dir)
251 shutil.copy(stateful_update_path, static_image_dir)
252 os.remove(update_path)
253 os.remove(stateful_update_path)
254 except Exception:
Chris Sosa7c931362010-10-11 19:49:01 -0700255 _LogMessage('Failed to move %s and %s to %s' % (update_path,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700256 stateful_update_path,
257 static_image_dir))
258 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700259
rtc@google.com21a5ca32009-11-04 18:23:23 +0000260 return True
rtc@google.comded22402009-10-26 22:36:21 +0000261
Chris Sosa0356d3b2010-09-16 15:46:22 -0700262 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
263 static_image_dir=None):
264 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000265
Chris Sosa0356d3b2010-09-16 15:46:22 -0700266 Args:
267 image_path: full path to the image.
268 move_to_static_dir: Moves the files from their dir to the static dir.
269 static_image_dir: the directory to move images to after generating.
270 Returns:
271 True if the update payload was created successfully.
272 """
Chris Sosa7c931362010-10-11 19:49:01 -0700273 _LogMessage('Generating update for image %s' % image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700274 update_path = self.GenerateUpdateFile(image_path)
275 stateful_update_path = self.GenerateStatefulFile(image_path)
276 if not update_path or not stateful_update_path:
Chris Sosa7c931362010-10-11 19:49:01 -0700277 _LogMessage('Failed to generate update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700278 return False
279
280 if move_to_static_dir:
281 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
282 static_image_dir)
283 else:
284 return True
285
286 def GenerateLatestUpdateImage(self, board_id, client_version,
287 static_image_dir=None):
288 """Generates an update using the latest image that has been built.
289
290 This will only generate an update if the newest update is newer than that
291 on the client or client_version is 'ForcedUpdate'.
292
293 Args:
294 board_id: Name of the board.
295 client_version: Current version of the client or 'ForcedUpdate'
296 static_image_dir: the directory to move images to after generating.
297 Returns:
298 True if the update payload was created successfully.
299 """
300 latest_image_dir = self._GetLatestImageDir(board_id)
301 latest_version = self._GetVersionFromDir(latest_image_dir)
302 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
303
Chris Sosa7c931362010-10-11 19:49:01 -0700304 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700305 latest_image_path)
306
307 # Check to see whether or not we should update.
308 if client_version != 'ForcedUpdate' and not self._CanUpdate(
309 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700310 _LogMessage('no update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700311 return False
312
313 cached_file_path = os.path.join(static_image_dir, 'update.gz')
314 if (os.path.exists(cached_file_path) and
315 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
316 return True
317
318 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
319 static_image_dir=static_image_dir)
320
321 def GenerateImageFromZip(self, static_image_dir):
322 """Generates an update from an image zip file.
323
324 This method assumes you have an image.zip in directory you are serving
325 from. If this file is newer than a previously cached file, it will unzip
326 this file, create a payload and serve it.
327
328 Args:
329 static_image_dir: Directory where the zip file exists.
330 Returns:
331 True if the update payload was created successfully.
332 """
Chris Sosa7c931362010-10-11 19:49:01 -0700333 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700334 image_path = os.path.join(static_image_dir, self._GetImageName())
335 cached_file_path = os.path.join(static_image_dir, 'update.gz')
336 zip_file_path = os.path.join(static_image_dir, 'image.zip')
337 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
338 return True
339
340 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700341 _LogMessage('unzip image.zip failed.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700342 return False
343
344 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
345 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700346
Andrew de los Reyes52620802010-04-12 13:40:07 -0700347 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
348 """Imports a factory-floor server configuration file. The file should
349 be in this format:
350 config = [
351 {
352 'qual_ids': set([1, 2, 3, "x86-generic"]),
353 'factory_image': 'generic-factory.gz',
354 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
355 'release_image': 'generic-release.gz',
356 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
357 'oempartitionimg_image': 'generic-oem.gz',
358 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700359 'efipartitionimg_image': 'generic-efi.gz',
360 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700361 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800362 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800363 'firmware_image': 'generic-firmware.gz',
364 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700365 },
366 {
367 'qual_ids': set([6]),
368 'factory_image': '6-factory.gz',
369 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
370 'release_image': '6-release.gz',
371 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
372 'oempartitionimg_image': '6-oem.gz',
373 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700374 'efipartitionimg_image': '6-efi.gz',
375 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700376 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800377 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800378 'firmware_image': '6-firmware.gz',
379 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700380 },
381 ]
382 The server will look for the files by name in the static files
383 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700384
Andrew de los Reyes52620802010-04-12 13:40:07 -0700385 If validate_checksums is True, validates checksums and exits. If
386 a checksum mismatch is found, it's printed to the screen.
387 """
388 f = open(filename, 'r')
389 output = {}
390 exec(f.read(), output)
391 self.factory_config = output['config']
392 success = True
393 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800394 for key in stanza.copy().iterkeys():
395 suffix = '_image'
396 if key.endswith(suffix):
397 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700398 stanza[kind + '_size'] = self._GetSize(os.path.join(
399 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800400 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700401 factory_checksum = self._GetHash(os.path.join(self.static_dir,
402 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800403 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700404 print ('Error: checksum mismatch for %s. Expected "%s" but file '
405 'has checksum "%s".' % (stanza[kind + '_image'],
406 stanza[kind + '_checksum'],
407 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800408 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700409
Andrew de los Reyes52620802010-04-12 13:40:07 -0700410 if validate_checksums:
411 if success is False:
412 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700413
Andrew de los Reyes52620802010-04-12 13:40:07 -0700414 print 'Config file looks good.'
415
416 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700417 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700418 for stanza in self.factory_config:
419 if board_id not in stanza['qual_ids']:
420 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700421 if kind + '_image' not in stanza:
422 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700423 return (stanza[kind + '_image'],
424 stanza[kind + '_checksum'],
425 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700426 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000427
Chris Sosa7c931362010-10-11 19:49:01 -0700428 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700429 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
430 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700431 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700432 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700433 url = '%s/static/%s' % (self.hostname, filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700434 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700435 # Factory install is using memento updater which is using the sha-1 hash so
436 # setting sha-256 to an empty string.
437 return self.GetUpdatePayload(checksum, '', size, url)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700438
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700439 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700440 """Handles an update ping from an update client.
441
442 Args:
443 data: xml blob from client.
444 label: optional label for the update.
445 Returns:
446 Update payload message for client.
447 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700448 # Set hostname as the hostname that the client is calling to and set up
449 # the url base.
450 self.hostname = cherrypy.request.base
451 if self.urlbase:
452 static_urlbase = self.urlbase
453 elif self.serve_only:
454 static_urlbase = '%s/static/archive' % self.hostname
455 else:
456 static_urlbase = '%s/static' % self.hostname
457
458 _LogMessage('Using static url base %s' % static_urlbase)
459 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700460
461 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700462 update_dom = minidom.parseString(data)
463 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700464 if (root.hasAttribute('updaterversion') and
465 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700466 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700467 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700468 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700469
470 # We only generate update payloads for updatecheck requests.
471 update_check = root.getElementsByTagName('o:updatecheck')
472 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700473 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700474 # TODO(sosa): Generate correct non-updatecheck payload to better test
475 # update clients.
476 return self.GetNoUpdatePayload()
477
478 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700479 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800480 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700481 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700482 board_id = (query.hasAttribute('board') and query.getAttribute('board')
483 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700484
Chris Sosa0356d3b2010-09-16 15:46:22 -0700485 # Separate logic as Factory requests have static url's that override
486 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700487 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700488 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700489 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700490 static_image_dir = self.static_dir
491 if label:
492 static_image_dir = os.path.join(static_image_dir, label)
493
Chris Sosa5d342a22010-09-28 16:54:41 -0700494 # Prefer cached image if it exists.
495 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
496 'update.gz')):
Chris Sosa7c931362010-10-11 19:49:01 -0700497 _LogMessage('Using cached image regardless of timestamps.')
Chris Sosa5d342a22010-09-28 16:54:41 -0700498 has_built_image = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700499 else:
Chris Sosa5d342a22010-09-28 16:54:41 -0700500 if self.forced_image:
501 has_built_image = self.GenerateUpdateImage(
502 self.forced_image, move_to_static_dir=True,
503 static_image_dir=static_image_dir)
504 # Now that we've generated it, clear out so that other pings of same
505 # devserver instance do not generate new images.
506 self.forced_image = None
507 elif self.serve_only:
508 has_built_image = self.GenerateImageFromZip(static_image_dir)
509 else:
510 has_built_image = self.GenerateLatestUpdateImage(board_id,
511 client_version,
512 static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700513
514 if has_built_image:
515 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz'))
Darin Petkov91436cb2010-09-28 08:52:17 -0700516 sha256 = self._GetSHA256(os.path.join(static_image_dir, 'update.gz'))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700517 size = self._GetSize(os.path.join(static_image_dir, 'update.gz'))
Chris Sosa5d342a22010-09-28 16:54:41 -0700518 if label:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700519 url = '%s/%s/update.gz' % (static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700520 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700521 url = '%s/update.gz' % static_urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700522
Chris Sosa7c931362010-10-11 19:49:01 -0700523 _LogMessage('Responding to client to use url %s to get image.' % url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700524 return self.GetUpdatePayload(hash, sha256, size, url)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700525 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700526 return self.GetNoUpdatePayload()