blob: b997ca39902e85a2e29aa2f34165a56d75e5f4f2 [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
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700144 def _IsDeltaFormatFile(self, filename):
145 try:
146 file_handle = open(filename, 'r')
147 delta_magic = 'CrAU'
148 magic = file_handle.read(len(delta_magic))
149 return magic == delta_magic
150 except Exception:
151 return False
152
Darin Petkov91436cb2010-09-28 08:52:17 -0700153 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
154 # it takes advantage of reduced I/O and multiple processors. Something like:
155 # % tee < FILE > /dev/null \
156 # >( openssl dgst -sha256 -binary | openssl base64 ) \
157 # >( openssl sha1 -binary | openssl base64 )
158 def _GetSHA256(self, update_path):
159 """Returns the sha256 of the file given."""
160 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
161 update_path)
162 return os.popen(cmd).read().rstrip()
163
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700164 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700165 """Returns a payload to the client corresponding to a new update.
166
167 Args:
168 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700169 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700170 size: size of update blob
171 url: where to find update blob
172 Returns:
173 Xml string to be passed back to client.
174 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700175 delta = 'false'
176 if is_delta_format:
177 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000178 payload = """<?xml version="1.0" encoding="UTF-8"?>
179 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700180 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000181 <app appid="{%s}" status="ok">
182 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700183 <updatecheck
184 codebase="%s"
185 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700186 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700187 needsadmin="false"
188 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700189 IsDelta="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000190 status="ok"/>
191 </app>
192 </gupdate>
193 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700194 return payload % (self._GetSecondsSinceMidnight(),
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700195 self.app_id, url, hash, sha256, size, delta)
rtc@google.comded22402009-10-26 22:36:21 +0000196
rtc@google.com21a5ca32009-11-04 18:23:23 +0000197 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700198 """Returns a payload to the client corresponding to no update."""
199 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
200 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
201 < daystart elapsed_seconds = "%s" />
202 < app appid = "{%s}" status = "ok" >
203 < ping status = "ok" />
204 < updatecheck status = "noupdate" />
205 </ app >
206 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000207 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700208 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000209
Chris Sosa0356d3b2010-09-16 15:46:22 -0700210 def GenerateUpdateFile(self, image_path):
211 """Generates an update gz given a full path to an image.
212
213 Args:
214 image_path: Full path to image.
215 Returns:
216 Path to created update_payload or None on error.
217 """
218 image_dir = os.path.dirname(image_path)
219 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa7c931362010-10-11 19:49:01 -0700220 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700221
222 mkupdate_command = (
223 '%s/cros_generate_update_payload --image=%s --output=%s '
224 '--patch_kernel' % (self.scripts_dir, image_path, update_path))
225 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700226 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700227 return None
228
229 return update_path
230
231 def GenerateStatefulFile(self, image_path):
232 """Generates a stateful update gz given a full path to an image.
233
234 Args:
235 image_path: Full path to image.
236 Returns:
237 Path to created stateful update_payload or None on error.
238 """
239 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
240
241 # Unpack to get stateful partition.
242 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
243 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
244 if os.system(mkstatefulupdate_command) == 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700245 _LogMessage('Successfully generated %s.gz' % stateful_partition_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700246 return '%s.gz' % stateful_partition_path
247
Chris Sosa7c931362010-10-11 19:49:01 -0700248 _LogMessage('Failed to create stateful update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700249 return None
250
251 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
252 static_image_dir):
253 """Moves gz files from their directories to serving directories.
254
255 Args:
256 update_path: full path to main update gz.
257 stateful_update_path: full path to stateful partition gz.
258 static_image_dir: where to put files.
259 Returns:
260 Returns True if the files were moved over successfully.
261 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700262 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700263 shutil.copy(update_path, static_image_dir)
264 shutil.copy(stateful_update_path, static_image_dir)
265 os.remove(update_path)
266 os.remove(stateful_update_path)
267 except Exception:
Chris Sosa7c931362010-10-11 19:49:01 -0700268 _LogMessage('Failed to move %s and %s to %s' % (update_path,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700269 stateful_update_path,
270 static_image_dir))
271 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700272
rtc@google.com21a5ca32009-11-04 18:23:23 +0000273 return True
rtc@google.comded22402009-10-26 22:36:21 +0000274
Chris Sosa0356d3b2010-09-16 15:46:22 -0700275 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
276 static_image_dir=None):
277 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000278
Chris Sosa0356d3b2010-09-16 15:46:22 -0700279 Args:
280 image_path: full path to the image.
281 move_to_static_dir: Moves the files from their dir to the static dir.
282 static_image_dir: the directory to move images to after generating.
283 Returns:
284 True if the update payload was created successfully.
285 """
Chris Sosa7c931362010-10-11 19:49:01 -0700286 _LogMessage('Generating update for image %s' % image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700287 update_path = self.GenerateUpdateFile(image_path)
288 stateful_update_path = self.GenerateStatefulFile(image_path)
289 if not update_path or not stateful_update_path:
Chris Sosa7c931362010-10-11 19:49:01 -0700290 _LogMessage('Failed to generate update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700291 return False
292
293 if move_to_static_dir:
294 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
295 static_image_dir)
296 else:
297 return True
298
299 def GenerateLatestUpdateImage(self, board_id, client_version,
300 static_image_dir=None):
301 """Generates an update using the latest image that has been built.
302
303 This will only generate an update if the newest update is newer than that
304 on the client or client_version is 'ForcedUpdate'.
305
306 Args:
307 board_id: Name of the board.
308 client_version: Current version of the client or 'ForcedUpdate'
309 static_image_dir: the directory to move images to after generating.
310 Returns:
311 True if the update payload was created successfully.
312 """
313 latest_image_dir = self._GetLatestImageDir(board_id)
314 latest_version = self._GetVersionFromDir(latest_image_dir)
315 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
316
Chris Sosa7c931362010-10-11 19:49:01 -0700317 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700318 latest_image_path)
319
320 # Check to see whether or not we should update.
321 if client_version != 'ForcedUpdate' and not self._CanUpdate(
322 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700323 _LogMessage('no update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700324 return False
325
326 cached_file_path = os.path.join(static_image_dir, 'update.gz')
327 if (os.path.exists(cached_file_path) and
328 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
329 return True
330
331 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
332 static_image_dir=static_image_dir)
333
334 def GenerateImageFromZip(self, static_image_dir):
335 """Generates an update from an image zip file.
336
337 This method assumes you have an image.zip in directory you are serving
338 from. If this file is newer than a previously cached file, it will unzip
339 this file, create a payload and serve it.
340
341 Args:
342 static_image_dir: Directory where the zip file exists.
343 Returns:
344 True if the update payload was created successfully.
345 """
Chris Sosa7c931362010-10-11 19:49:01 -0700346 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700347 image_path = os.path.join(static_image_dir, self._GetImageName())
348 cached_file_path = os.path.join(static_image_dir, 'update.gz')
349 zip_file_path = os.path.join(static_image_dir, 'image.zip')
350 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
351 return True
352
353 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700354 _LogMessage('unzip image.zip failed.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700355 return False
356
357 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
358 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700359
Andrew de los Reyes52620802010-04-12 13:40:07 -0700360 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
361 """Imports a factory-floor server configuration file. The file should
362 be in this format:
363 config = [
364 {
365 'qual_ids': set([1, 2, 3, "x86-generic"]),
366 'factory_image': 'generic-factory.gz',
367 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
368 'release_image': 'generic-release.gz',
369 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
370 'oempartitionimg_image': 'generic-oem.gz',
371 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700372 'efipartitionimg_image': 'generic-efi.gz',
373 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700374 'stateimg_image': 'generic-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': 'generic-firmware.gz',
377 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700378 },
379 {
380 'qual_ids': set([6]),
381 'factory_image': '6-factory.gz',
382 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
383 'release_image': '6-release.gz',
384 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
385 'oempartitionimg_image': '6-oem.gz',
386 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700387 'efipartitionimg_image': '6-efi.gz',
388 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700389 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800390 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800391 'firmware_image': '6-firmware.gz',
392 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700393 },
394 ]
395 The server will look for the files by name in the static files
396 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700397
Andrew de los Reyes52620802010-04-12 13:40:07 -0700398 If validate_checksums is True, validates checksums and exits. If
399 a checksum mismatch is found, it's printed to the screen.
400 """
401 f = open(filename, 'r')
402 output = {}
403 exec(f.read(), output)
404 self.factory_config = output['config']
405 success = True
406 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800407 for key in stanza.copy().iterkeys():
408 suffix = '_image'
409 if key.endswith(suffix):
410 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700411 stanza[kind + '_size'] = self._GetSize(os.path.join(
412 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800413 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700414 factory_checksum = self._GetHash(os.path.join(self.static_dir,
415 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800416 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700417 print ('Error: checksum mismatch for %s. Expected "%s" but file '
418 'has checksum "%s".' % (stanza[kind + '_image'],
419 stanza[kind + '_checksum'],
420 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800421 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700422
Andrew de los Reyes52620802010-04-12 13:40:07 -0700423 if validate_checksums:
424 if success is False:
425 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700426
Andrew de los Reyes52620802010-04-12 13:40:07 -0700427 print 'Config file looks good.'
428
429 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700430 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700431 for stanza in self.factory_config:
432 if board_id not in stanza['qual_ids']:
433 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700434 if kind + '_image' not in stanza:
435 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700436 return (stanza[kind + '_image'],
437 stanza[kind + '_checksum'],
438 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700439 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000440
Chris Sosa7c931362010-10-11 19:49:01 -0700441 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700442 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
443 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700444 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700445 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700446 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700447 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700448 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700449 # Factory install is using memento updater which is using the sha-1 hash so
450 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700451 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700452
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700453 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700454 """Handles an update ping from an update client.
455
456 Args:
457 data: xml blob from client.
458 label: optional label for the update.
459 Returns:
460 Update payload message for client.
461 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700462 # Set hostname as the hostname that the client is calling to and set up
463 # the url base.
464 self.hostname = cherrypy.request.base
465 if self.urlbase:
466 static_urlbase = self.urlbase
467 elif self.serve_only:
468 static_urlbase = '%s/static/archive' % self.hostname
469 else:
470 static_urlbase = '%s/static' % self.hostname
471
472 _LogMessage('Using static url base %s' % static_urlbase)
473 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700474
475 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700476 update_dom = minidom.parseString(data)
477 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700478 if (root.hasAttribute('updaterversion') and
479 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700480 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700481 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700482 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700483
484 # We only generate update payloads for updatecheck requests.
485 update_check = root.getElementsByTagName('o:updatecheck')
486 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700487 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700488 # TODO(sosa): Generate correct non-updatecheck payload to better test
489 # update clients.
490 return self.GetNoUpdatePayload()
491
492 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700493 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800494 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700495 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700496 board_id = (query.hasAttribute('board') and query.getAttribute('board')
497 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700498
Chris Sosa0356d3b2010-09-16 15:46:22 -0700499 # Separate logic as Factory requests have static url's that override
500 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700501 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700502 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700503 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700504 static_image_dir = self.static_dir
505 if label:
506 static_image_dir = os.path.join(static_image_dir, label)
507
Chris Sosa5d342a22010-09-28 16:54:41 -0700508 # Prefer cached image if it exists.
509 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
510 'update.gz')):
Chris Sosa7c931362010-10-11 19:49:01 -0700511 _LogMessage('Using cached image regardless of timestamps.')
Chris Sosa5d342a22010-09-28 16:54:41 -0700512 has_built_image = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700513 else:
Chris Sosa5d342a22010-09-28 16:54:41 -0700514 if self.forced_image:
515 has_built_image = self.GenerateUpdateImage(
516 self.forced_image, move_to_static_dir=True,
517 static_image_dir=static_image_dir)
518 # Now that we've generated it, clear out so that other pings of same
519 # devserver instance do not generate new images.
520 self.forced_image = None
521 elif self.serve_only:
522 has_built_image = self.GenerateImageFromZip(static_image_dir)
523 else:
524 has_built_image = self.GenerateLatestUpdateImage(board_id,
525 client_version,
526 static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700527
528 if has_built_image:
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700529 filename = os.path.join(static_image_dir, 'update.gz')
530 hash = self._GetHash(filename)
531 sha256 = self._GetSHA256(filename)
532 size = self._GetSize(filename)
533 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700534 if label:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700535 url = '%s/%s/update.gz' % (static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700536 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700537 url = '%s/update.gz' % static_urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700538
Chris Sosa7c931362010-10-11 19:49:01 -0700539 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700540 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700541 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700542 return self.GetNoUpdatePayload()