blob: ca5c14447ae360811740b3708af31d5c65ba1327 [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 Sosa7c931362010-10-11 19:49:01 -070039 self.hostname = '%s:%s' % (socket.gethostname(), port)
Chris Sosa5d342a22010-09-28 16:54:41 -070040 if urlbase:
41 self.static_urlbase = urlbase
42 elif self.serve_only:
Chris Sosa7c931362010-10-11 19:49:01 -070043 self.static_urlbase = 'http://%s/static/archive' % self.hostname
Chris Sosa5d342a22010-09-28 16:54:41 -070044 else:
Chris Sosa7c931362010-10-11 19:49:01 -070045 self.static_urlbase = 'http://%s/static' % self.hostname
Chris Sosa5d342a22010-09-28 16:54:41 -070046
Chris Sosab63a9282010-09-02 10:43:23 -070047 self.client_prefix = client_prefix
Chris Sosa0356d3b2010-09-16 15:46:22 -070048 self.forced_image = forced_image
Chris Sosa5d342a22010-09-28 16:54:41 -070049 self.use_cached = use_cached
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070050
Chris Sosa0356d3b2010-09-16 15:46:22 -070051 def _GetSecondsSinceMidnight(self):
52 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070053 now = time.localtime()
54 return now[3] * 3600 + now[4] * 60 + now[5]
55
Chris Sosa0356d3b2010-09-16 15:46:22 -070056 def _GetDefaultBoardID(self):
57 """Returns the default board id stored in .default_board."""
58 board_file = '%s/.default_board' % (self.scripts_dir)
59 try:
60 return open(board_file).read()
61 except IOError:
62 return 'x86-generic'
63
64 def _GetLatestImageDir(self, board_id):
65 """Returns the latest image dir based on shell script."""
66 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
67 return os.popen(cmd).read().strip()
68
69 def _GetVersionFromDir(self, image_dir):
70 """Returns the version of the image based on the name of the directory."""
71 latest_version = os.path.basename(image_dir)
72 return latest_version.split('-')[0]
73
74 def _CanUpdate(self, client_version, latest_version):
75 """Returns true if the latest_version is greater than the client_version."""
76 client_tokens = client_version.replace('_', '').split('.')
77 latest_tokens = latest_version.replace('_', '').split('.')
Chris Sosa7c931362010-10-11 19:49:01 -070078 _LogMessage('client version %s latest version %s'
Chris Sosa0356d3b2010-09-16 15:46:22 -070079 % (client_version, latest_version))
80 for i in range(4):
81 if int(latest_tokens[i]) == int(client_tokens[i]):
82 continue
83 return int(latest_tokens[i]) > int(client_tokens[i])
84 return False
85
86 def _UnpackStatefulPartition(self, image_path, stateful_file):
87 """Given an image, unpacks its stateful partition to stateful_file."""
88 image_dir = os.path.dirname(image_path)
89 image_file = os.path.basename(image_path)
90
91 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
92 get_size = '$(cgpt show -s -i 1 %s)' % image_file
93 unpack_command = (
94 'cd %s && '
95 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
96 stateful_file, get_offset,
97 get_size))
Chris Sosa7c931362010-10-11 19:49:01 -070098 _LogMessage(unpack_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -070099 return os.system(unpack_command) == 0
100
101 def _UnpackZip(self, image_dir):
102 """Unpacks an image.zip into a given directory."""
103 image = os.path.join(image_dir, self._GetImageName())
104 if os.path.exists(image):
105 return True
106 else:
107 # -n, never clobber an existing file, in case we get invoked
108 # simultaneously by multiple request handlers. This means that
109 # we're assuming each image.zip file lives in a versioned
110 # directory (a la Buildbot).
111 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
112
113 def _GetImageName(self):
114 """Returns the name of the image that should be used."""
115 if self.use_test_image:
116 image_name = 'chromiumos_test_image.bin'
117 else:
118 image_name = 'chromiumos_image.bin'
119 return image_name
120
121 def _IsImageNewerThanCached(self, image_path, cached_file_path):
122 """Returns true if the image is newer than the cached image."""
123 if os.path.exists(cached_file_path) and os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700124 _LogMessage('Usable cached image found at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700125 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
126 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
127 raise Exception('Image does not exist and cached image missing')
128 else:
129 # Only one is missing, figure out which one.
130 if os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700131 _LogMessage('No cached image found - image generation required.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700132 return True
133 else:
Chris Sosa7c931362010-10-11 19:49:01 -0700134 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700135 return False
136
137 def _GetSize(self, update_path):
138 """Returns the size of the file given."""
139 return os.path.getsize(update_path)
140
141 def _GetHash(self, update_path):
142 """Returns the sha1 of the file given."""
143 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
144 % update_path)
145 return os.popen(cmd).read().rstrip()
146
Darin Petkov91436cb2010-09-28 08:52:17 -0700147 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
148 # it takes advantage of reduced I/O and multiple processors. Something like:
149 # % tee < FILE > /dev/null \
150 # >( openssl dgst -sha256 -binary | openssl base64 ) \
151 # >( openssl sha1 -binary | openssl base64 )
152 def _GetSHA256(self, update_path):
153 """Returns the sha256 of the file given."""
154 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
155 update_path)
156 return os.popen(cmd).read().rstrip()
157
158 def GetUpdatePayload(self, hash, sha256, size, url):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700159 """Returns a payload to the client corresponding to a new update.
160
161 Args:
162 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700163 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700164 size: size of update blob
165 url: where to find update blob
166 Returns:
167 Xml string to be passed back to client.
168 """
rtc@google.com21a5ca32009-11-04 18:23:23 +0000169 payload = """<?xml version="1.0" encoding="UTF-8"?>
170 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700171 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000172 <app appid="{%s}" status="ok">
173 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700174 <updatecheck
175 codebase="%s"
176 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700177 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700178 needsadmin="false"
179 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000180 status="ok"/>
181 </app>
182 </gupdate>
183 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700184 return payload % (self._GetSecondsSinceMidnight(),
Darin Petkov91436cb2010-09-28 08:52:17 -0700185 self.app_id, url, hash, sha256, size)
rtc@google.comded22402009-10-26 22:36:21 +0000186
rtc@google.com21a5ca32009-11-04 18:23:23 +0000187 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700188 """Returns a payload to the client corresponding to no update."""
189 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
190 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
191 < daystart elapsed_seconds = "%s" />
192 < app appid = "{%s}" status = "ok" >
193 < ping status = "ok" />
194 < updatecheck status = "noupdate" />
195 </ app >
196 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000197 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700198 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000199
Chris Sosa0356d3b2010-09-16 15:46:22 -0700200 def GenerateUpdateFile(self, image_path):
201 """Generates an update gz given a full path to an image.
202
203 Args:
204 image_path: Full path to image.
205 Returns:
206 Path to created update_payload or None on error.
207 """
208 image_dir = os.path.dirname(image_path)
209 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa7c931362010-10-11 19:49:01 -0700210 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700211
212 mkupdate_command = (
213 '%s/cros_generate_update_payload --image=%s --output=%s '
214 '--patch_kernel' % (self.scripts_dir, image_path, update_path))
215 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700216 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700217 return None
218
219 return update_path
220
221 def GenerateStatefulFile(self, image_path):
222 """Generates a stateful update gz given a full path to an image.
223
224 Args:
225 image_path: Full path to image.
226 Returns:
227 Path to created stateful update_payload or None on error.
228 """
229 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
230
231 # Unpack to get stateful partition.
232 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
233 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
234 if os.system(mkstatefulupdate_command) == 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700235 _LogMessage('Successfully generated %s.gz' % stateful_partition_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700236 return '%s.gz' % stateful_partition_path
237
Chris Sosa7c931362010-10-11 19:49:01 -0700238 _LogMessage('Failed to create stateful update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700239 return None
240
241 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
242 static_image_dir):
243 """Moves gz files from their directories to serving directories.
244
245 Args:
246 update_path: full path to main update gz.
247 stateful_update_path: full path to stateful partition gz.
248 static_image_dir: where to put files.
249 Returns:
250 Returns True if the files were moved over successfully.
251 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700252 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700253 shutil.copy(update_path, static_image_dir)
254 shutil.copy(stateful_update_path, static_image_dir)
255 os.remove(update_path)
256 os.remove(stateful_update_path)
257 except Exception:
Chris Sosa7c931362010-10-11 19:49:01 -0700258 _LogMessage('Failed to move %s and %s to %s' % (update_path,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700259 stateful_update_path,
260 static_image_dir))
261 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700262
rtc@google.com21a5ca32009-11-04 18:23:23 +0000263 return True
rtc@google.comded22402009-10-26 22:36:21 +0000264
Chris Sosa0356d3b2010-09-16 15:46:22 -0700265 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
266 static_image_dir=None):
267 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000268
Chris Sosa0356d3b2010-09-16 15:46:22 -0700269 Args:
270 image_path: full path to the image.
271 move_to_static_dir: Moves the files from their dir to the static dir.
272 static_image_dir: the directory to move images to after generating.
273 Returns:
274 True if the update payload was created successfully.
275 """
Chris Sosa7c931362010-10-11 19:49:01 -0700276 _LogMessage('Generating update for image %s' % image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700277 update_path = self.GenerateUpdateFile(image_path)
278 stateful_update_path = self.GenerateStatefulFile(image_path)
279 if not update_path or not stateful_update_path:
Chris Sosa7c931362010-10-11 19:49:01 -0700280 _LogMessage('Failed to generate update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700281 return False
282
283 if move_to_static_dir:
284 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
285 static_image_dir)
286 else:
287 return True
288
289 def GenerateLatestUpdateImage(self, board_id, client_version,
290 static_image_dir=None):
291 """Generates an update using the latest image that has been built.
292
293 This will only generate an update if the newest update is newer than that
294 on the client or client_version is 'ForcedUpdate'.
295
296 Args:
297 board_id: Name of the board.
298 client_version: Current version of the client or 'ForcedUpdate'
299 static_image_dir: the directory to move images to after generating.
300 Returns:
301 True if the update payload was created successfully.
302 """
303 latest_image_dir = self._GetLatestImageDir(board_id)
304 latest_version = self._GetVersionFromDir(latest_image_dir)
305 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
306
Chris Sosa7c931362010-10-11 19:49:01 -0700307 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700308 latest_image_path)
309
310 # Check to see whether or not we should update.
311 if client_version != 'ForcedUpdate' and not self._CanUpdate(
312 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700313 _LogMessage('no update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700314 return False
315
316 cached_file_path = os.path.join(static_image_dir, 'update.gz')
317 if (os.path.exists(cached_file_path) and
318 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
319 return True
320
321 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
322 static_image_dir=static_image_dir)
323
324 def GenerateImageFromZip(self, static_image_dir):
325 """Generates an update from an image zip file.
326
327 This method assumes you have an image.zip in directory you are serving
328 from. If this file is newer than a previously cached file, it will unzip
329 this file, create a payload and serve it.
330
331 Args:
332 static_image_dir: Directory where the zip file exists.
333 Returns:
334 True if the update payload was created successfully.
335 """
Chris Sosa7c931362010-10-11 19:49:01 -0700336 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700337 image_path = os.path.join(static_image_dir, self._GetImageName())
338 cached_file_path = os.path.join(static_image_dir, 'update.gz')
339 zip_file_path = os.path.join(static_image_dir, 'image.zip')
340 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
341 return True
342
343 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700344 _LogMessage('unzip image.zip failed.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700345 return False
346
347 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
348 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700349
Andrew de los Reyes52620802010-04-12 13:40:07 -0700350 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
351 """Imports a factory-floor server configuration file. The file should
352 be in this format:
353 config = [
354 {
355 'qual_ids': set([1, 2, 3, "x86-generic"]),
356 'factory_image': 'generic-factory.gz',
357 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
358 'release_image': 'generic-release.gz',
359 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
360 'oempartitionimg_image': 'generic-oem.gz',
361 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700362 'efipartitionimg_image': 'generic-efi.gz',
363 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700364 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800365 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800366 'firmware_image': 'generic-firmware.gz',
367 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700368 },
369 {
370 'qual_ids': set([6]),
371 'factory_image': '6-factory.gz',
372 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
373 'release_image': '6-release.gz',
374 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
375 'oempartitionimg_image': '6-oem.gz',
376 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700377 'efipartitionimg_image': '6-efi.gz',
378 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700379 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800380 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800381 'firmware_image': '6-firmware.gz',
382 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700383 },
384 ]
385 The server will look for the files by name in the static files
386 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700387
Andrew de los Reyes52620802010-04-12 13:40:07 -0700388 If validate_checksums is True, validates checksums and exits. If
389 a checksum mismatch is found, it's printed to the screen.
390 """
391 f = open(filename, 'r')
392 output = {}
393 exec(f.read(), output)
394 self.factory_config = output['config']
395 success = True
396 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800397 for key in stanza.copy().iterkeys():
398 suffix = '_image'
399 if key.endswith(suffix):
400 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700401 stanza[kind + '_size'] = self._GetSize(os.path.join(
402 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800403 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700404 factory_checksum = self._GetHash(os.path.join(self.static_dir,
405 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800406 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700407 print ('Error: checksum mismatch for %s. Expected "%s" but file '
408 'has checksum "%s".' % (stanza[kind + '_image'],
409 stanza[kind + '_checksum'],
410 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800411 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700412
Andrew de los Reyes52620802010-04-12 13:40:07 -0700413 if validate_checksums:
414 if success is False:
415 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700416
Andrew de los Reyes52620802010-04-12 13:40:07 -0700417 print 'Config file looks good.'
418
419 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700420 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700421 for stanza in self.factory_config:
422 if board_id not in stanza['qual_ids']:
423 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700424 if kind + '_image' not in stanza:
425 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700426 return (stanza[kind + '_image'],
427 stanza[kind + '_checksum'],
428 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700429 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000430
Chris Sosa7c931362010-10-11 19:49:01 -0700431 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700432 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
433 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700434 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700435 return self.GetNoUpdatePayload()
Chris Sosa7c931362010-10-11 19:49:01 -0700436 url = 'http://%s/static/%s' % (self.hostname, filename)
437 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700438 # Factory install is using memento updater which is using the sha-1 hash so
439 # setting sha-256 to an empty string.
440 return self.GetUpdatePayload(checksum, '', size, url)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700441
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700442 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700443 """Handles an update ping from an update client.
444
445 Args:
446 data: xml blob from client.
447 label: optional label for the update.
448 Returns:
449 Update payload message for client.
450 """
Chris Sosa7c931362010-10-11 19:49:01 -0700451 _LogMessage('handling update ping: %s' % data)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000452 update_dom = minidom.parseString(data)
453 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700454
455 # Check the client prefix to make sure you can support this type of update.
456 if (root.hasAttribute('updaterversion') and
457 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700458 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700459 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700460 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700461
462 # We only generate update payloads for updatecheck requests.
463 update_check = root.getElementsByTagName('o:updatecheck')
464 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700465 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700466 # TODO(sosa): Generate correct non-updatecheck payload to better test
467 # update clients.
468 return self.GetNoUpdatePayload()
469
470 # Since this is an updatecheck, get information about the requester.
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 Sosa7c931362010-10-11 19:49:01 -0700480 return self.HandleFactoryRequest(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')):
Chris Sosa7c931362010-10-11 19:49:01 -0700489 _LogMessage('Using cached image regardless of timestamps.')
Chris Sosa5d342a22010-09-28 16:54:41 -0700490 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
Chris Sosa7c931362010-10-11 19:49:01 -0700515 _LogMessage('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()