blob: 498b0e947af858d8cd9c9e37ba18013147d1767a [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 Sosa62f720b2010-10-26 21:39:48 -070034 use_cached=False, port=8080, src_image='', *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
Chris Sosa62f720b2010-10-26 21:39:48 -070047 self.src_image = src_image
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070048
Chris Sosa0356d3b2010-09-16 15:46:22 -070049 def _GetSecondsSinceMidnight(self):
50 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070051 now = time.localtime()
52 return now[3] * 3600 + now[4] * 60 + now[5]
53
Chris Sosa0356d3b2010-09-16 15:46:22 -070054 def _GetDefaultBoardID(self):
55 """Returns the default board id stored in .default_board."""
56 board_file = '%s/.default_board' % (self.scripts_dir)
57 try:
58 return open(board_file).read()
59 except IOError:
60 return 'x86-generic'
61
62 def _GetLatestImageDir(self, board_id):
63 """Returns the latest image dir based on shell script."""
64 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
65 return os.popen(cmd).read().strip()
66
67 def _GetVersionFromDir(self, image_dir):
68 """Returns the version of the image based on the name of the directory."""
69 latest_version = os.path.basename(image_dir)
70 return latest_version.split('-')[0]
71
72 def _CanUpdate(self, client_version, latest_version):
73 """Returns true if the latest_version is greater than the client_version."""
74 client_tokens = client_version.replace('_', '').split('.')
75 latest_tokens = latest_version.replace('_', '').split('.')
Chris Sosa7c931362010-10-11 19:49:01 -070076 _LogMessage('client version %s latest version %s'
Chris Sosa0356d3b2010-09-16 15:46:22 -070077 % (client_version, latest_version))
78 for i in range(4):
79 if int(latest_tokens[i]) == int(client_tokens[i]):
80 continue
81 return int(latest_tokens[i]) > int(client_tokens[i])
82 return False
83
84 def _UnpackStatefulPartition(self, image_path, stateful_file):
85 """Given an image, unpacks its stateful partition to stateful_file."""
86 image_dir = os.path.dirname(image_path)
87 image_file = os.path.basename(image_path)
88
89 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
90 get_size = '$(cgpt show -s -i 1 %s)' % image_file
91 unpack_command = (
92 'cd %s && '
93 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
94 stateful_file, get_offset,
95 get_size))
Chris Sosa7c931362010-10-11 19:49:01 -070096 _LogMessage(unpack_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -070097 return os.system(unpack_command) == 0
98
99 def _UnpackZip(self, image_dir):
100 """Unpacks an image.zip into a given directory."""
101 image = os.path.join(image_dir, self._GetImageName())
102 if os.path.exists(image):
103 return True
104 else:
105 # -n, never clobber an existing file, in case we get invoked
106 # simultaneously by multiple request handlers. This means that
107 # we're assuming each image.zip file lives in a versioned
108 # directory (a la Buildbot).
109 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
110
111 def _GetImageName(self):
112 """Returns the name of the image that should be used."""
113 if self.use_test_image:
114 image_name = 'chromiumos_test_image.bin'
115 else:
116 image_name = 'chromiumos_image.bin'
117 return image_name
118
119 def _IsImageNewerThanCached(self, image_path, cached_file_path):
120 """Returns true if the image is newer than the cached image."""
121 if os.path.exists(cached_file_path) and os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700122 _LogMessage('Usable cached image found at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700123 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
124 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
125 raise Exception('Image does not exist and cached image missing')
126 else:
127 # Only one is missing, figure out which one.
128 if os.path.exists(image_path):
Chris Sosa7c931362010-10-11 19:49:01 -0700129 _LogMessage('No cached image found - image generation required.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700130 return True
131 else:
Chris Sosa7c931362010-10-11 19:49:01 -0700132 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700133 return False
134
135 def _GetSize(self, update_path):
136 """Returns the size of the file given."""
137 return os.path.getsize(update_path)
138
139 def _GetHash(self, update_path):
140 """Returns the sha1 of the file given."""
141 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
142 % update_path)
143 return os.popen(cmd).read().rstrip()
144
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700145 def _IsDeltaFormatFile(self, filename):
146 try:
147 file_handle = open(filename, 'r')
148 delta_magic = 'CrAU'
149 magic = file_handle.read(len(delta_magic))
150 return magic == delta_magic
151 except Exception:
152 return False
153
Darin Petkov91436cb2010-09-28 08:52:17 -0700154 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
155 # it takes advantage of reduced I/O and multiple processors. Something like:
156 # % tee < FILE > /dev/null \
157 # >( openssl dgst -sha256 -binary | openssl base64 ) \
158 # >( openssl sha1 -binary | openssl base64 )
159 def _GetSHA256(self, update_path):
160 """Returns the sha256 of the file given."""
161 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
162 update_path)
163 return os.popen(cmd).read().rstrip()
164
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700165 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700166 """Returns a payload to the client corresponding to a new update.
167
168 Args:
169 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700170 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700171 size: size of update blob
172 url: where to find update blob
173 Returns:
174 Xml string to be passed back to client.
175 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700176 delta = 'false'
177 if is_delta_format:
178 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000179 payload = """<?xml version="1.0" encoding="UTF-8"?>
180 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700181 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000182 <app appid="{%s}" status="ok">
183 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700184 <updatecheck
185 codebase="%s"
186 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700187 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700188 needsadmin="false"
189 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700190 IsDelta="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000191 status="ok"/>
192 </app>
193 </gupdate>
194 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700195 return payload % (self._GetSecondsSinceMidnight(),
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700196 self.app_id, url, hash, sha256, size, delta)
rtc@google.comded22402009-10-26 22:36:21 +0000197
rtc@google.com21a5ca32009-11-04 18:23:23 +0000198 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700199 """Returns a payload to the client corresponding to no update."""
200 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
201 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
202 < daystart elapsed_seconds = "%s" />
203 < app appid = "{%s}" status = "ok" >
204 < ping status = "ok" />
205 < updatecheck status = "noupdate" />
206 </ app >
207 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000208 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700209 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000210
Chris Sosa0356d3b2010-09-16 15:46:22 -0700211 def GenerateUpdateFile(self, image_path):
212 """Generates an update gz given a full path to an image.
213
214 Args:
215 image_path: Full path to image.
216 Returns:
217 Path to created update_payload or None on error.
218 """
219 image_dir = os.path.dirname(image_path)
220 update_path = os.path.join(image_dir, 'update.gz')
Chris Sosa7c931362010-10-11 19:49:01 -0700221 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700222
223 mkupdate_command = (
Chris Sosa62f720b2010-10-26 21:39:48 -0700224 '%s/cros_generate_update_payload --image="%s" --output="%s" '
225 '--patch_kernel --noold_style --src_image="%s"' % (
226 self.scripts_dir, image_path,
227 update_path, self.src_image))
228 _LogMessage(mkupdate_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700229 if os.system(mkupdate_command) != 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700230 _LogMessage('Failed to create base update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700231 return None
232
233 return update_path
234
235 def GenerateStatefulFile(self, image_path):
236 """Generates a stateful update gz given a full path to an image.
237
238 Args:
239 image_path: Full path to image.
240 Returns:
241 Path to created stateful update_payload or None on error.
242 """
243 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
244
245 # Unpack to get stateful partition.
246 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
247 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
248 if os.system(mkstatefulupdate_command) == 0:
Chris Sosa7c931362010-10-11 19:49:01 -0700249 _LogMessage('Successfully generated %s.gz' % stateful_partition_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700250 return '%s.gz' % stateful_partition_path
251
Chris Sosa7c931362010-10-11 19:49:01 -0700252 _LogMessage('Failed to create stateful update file')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700253 return None
254
255 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
256 static_image_dir):
257 """Moves gz files from their directories to serving directories.
258
259 Args:
260 update_path: full path to main update gz.
261 stateful_update_path: full path to stateful partition gz.
262 static_image_dir: where to put files.
263 Returns:
264 Returns True if the files were moved over successfully.
265 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700266 try:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700267 shutil.copy(update_path, static_image_dir)
268 shutil.copy(stateful_update_path, static_image_dir)
269 os.remove(update_path)
270 os.remove(stateful_update_path)
271 except Exception:
Chris Sosa7c931362010-10-11 19:49:01 -0700272 _LogMessage('Failed to move %s and %s to %s' % (update_path,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700273 stateful_update_path,
274 static_image_dir))
275 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700276
rtc@google.com21a5ca32009-11-04 18:23:23 +0000277 return True
rtc@google.comded22402009-10-26 22:36:21 +0000278
Chris Sosa0356d3b2010-09-16 15:46:22 -0700279 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
280 static_image_dir=None):
281 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000282
Chris Sosa0356d3b2010-09-16 15:46:22 -0700283 Args:
284 image_path: full path to the image.
285 move_to_static_dir: Moves the files from their dir to the static dir.
286 static_image_dir: the directory to move images to after generating.
287 Returns:
288 True if the update payload was created successfully.
289 """
Chris Sosa7c931362010-10-11 19:49:01 -0700290 _LogMessage('Generating update for image %s' % image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700291 update_path = self.GenerateUpdateFile(image_path)
292 stateful_update_path = self.GenerateStatefulFile(image_path)
293 if not update_path or not stateful_update_path:
Chris Sosa7c931362010-10-11 19:49:01 -0700294 _LogMessage('Failed to generate update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700295 return False
296
297 if move_to_static_dir:
298 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
299 static_image_dir)
300 else:
301 return True
302
303 def GenerateLatestUpdateImage(self, board_id, client_version,
304 static_image_dir=None):
305 """Generates an update using the latest image that has been built.
306
307 This will only generate an update if the newest update is newer than that
308 on the client or client_version is 'ForcedUpdate'.
309
310 Args:
311 board_id: Name of the board.
312 client_version: Current version of the client or 'ForcedUpdate'
313 static_image_dir: the directory to move images to after generating.
314 Returns:
315 True if the update payload was created successfully.
316 """
317 latest_image_dir = self._GetLatestImageDir(board_id)
318 latest_version = self._GetVersionFromDir(latest_image_dir)
319 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
320
Chris Sosa7c931362010-10-11 19:49:01 -0700321 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700322 latest_image_path)
323
324 # Check to see whether or not we should update.
325 if client_version != 'ForcedUpdate' and not self._CanUpdate(
326 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700327 _LogMessage('no update')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700328 return False
329
330 cached_file_path = os.path.join(static_image_dir, 'update.gz')
331 if (os.path.exists(cached_file_path) and
332 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
333 return True
334
335 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
336 static_image_dir=static_image_dir)
337
338 def GenerateImageFromZip(self, static_image_dir):
339 """Generates an update from an image zip file.
340
341 This method assumes you have an image.zip in directory you are serving
342 from. If this file is newer than a previously cached file, it will unzip
343 this file, create a payload and serve it.
344
345 Args:
346 static_image_dir: Directory where the zip file exists.
347 Returns:
348 True if the update payload was created successfully.
349 """
Chris Sosa7c931362010-10-11 19:49:01 -0700350 _LogMessage('Preparing to generate update from zip in %s.' % static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700351 image_path = os.path.join(static_image_dir, self._GetImageName())
352 cached_file_path = os.path.join(static_image_dir, 'update.gz')
353 zip_file_path = os.path.join(static_image_dir, 'image.zip')
354 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
355 return True
356
357 if not self._UnpackZip(static_image_dir):
Chris Sosa7c931362010-10-11 19:49:01 -0700358 _LogMessage('unzip image.zip failed.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700359 return False
360
361 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
362 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700363
Andrew de los Reyes52620802010-04-12 13:40:07 -0700364 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
365 """Imports a factory-floor server configuration file. The file should
366 be in this format:
367 config = [
368 {
369 'qual_ids': set([1, 2, 3, "x86-generic"]),
370 'factory_image': 'generic-factory.gz',
371 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
372 'release_image': 'generic-release.gz',
373 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
374 'oempartitionimg_image': 'generic-oem.gz',
375 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700376 'efipartitionimg_image': 'generic-efi.gz',
377 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700378 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800379 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800380 'firmware_image': 'generic-firmware.gz',
381 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700382 },
383 {
384 'qual_ids': set([6]),
385 'factory_image': '6-factory.gz',
386 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
387 'release_image': '6-release.gz',
388 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
389 'oempartitionimg_image': '6-oem.gz',
390 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700391 'efipartitionimg_image': '6-efi.gz',
392 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700393 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800394 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800395 'firmware_image': '6-firmware.gz',
396 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700397 },
398 ]
399 The server will look for the files by name in the static files
400 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700401
Andrew de los Reyes52620802010-04-12 13:40:07 -0700402 If validate_checksums is True, validates checksums and exits. If
403 a checksum mismatch is found, it's printed to the screen.
404 """
405 f = open(filename, 'r')
406 output = {}
407 exec(f.read(), output)
408 self.factory_config = output['config']
409 success = True
410 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800411 for key in stanza.copy().iterkeys():
412 suffix = '_image'
413 if key.endswith(suffix):
414 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700415 stanza[kind + '_size'] = self._GetSize(os.path.join(
416 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800417 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700418 factory_checksum = self._GetHash(os.path.join(self.static_dir,
419 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800420 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700421 print ('Error: checksum mismatch for %s. Expected "%s" but file '
422 'has checksum "%s".' % (stanza[kind + '_image'],
423 stanza[kind + '_checksum'],
424 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800425 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700426
Andrew de los Reyes52620802010-04-12 13:40:07 -0700427 if validate_checksums:
428 if success is False:
429 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700430
Andrew de los Reyes52620802010-04-12 13:40:07 -0700431 print 'Config file looks good.'
432
433 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700434 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700435 for stanza in self.factory_config:
436 if board_id not in stanza['qual_ids']:
437 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700438 if kind + '_image' not in stanza:
439 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700440 return (stanza[kind + '_image'],
441 stanza[kind + '_checksum'],
442 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700443 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000444
Chris Sosa7c931362010-10-11 19:49:01 -0700445 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700446 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
447 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700448 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700449 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700450 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700451 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700452 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700453 # Factory install is using memento updater which is using the sha-1 hash so
454 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700455 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700456
Chris Sosa2c048f12010-10-27 16:05:27 -0700457 def GenerateUpdatePayloadForNonFactory(self, static_image_dir):
458 """Generates an update for non-factory and returns True on success."""
459 if self.use_cached and os.path.exists(os.path.join(static_image_dir,
460 'update.gz')):
461 _LogMessage('Using cached image regardless of timestamps.')
462 return True
463 else:
464 if self.forced_image:
465 has_built_image = self.GenerateUpdateImage(
466 self.forced_image, move_to_static_dir=True,
467 static_image_dir=static_image_dir)
468 # Now that we've generated it, force devserver to use it.
469 self.use_cached = True
470 elif self.serve_only:
471 return self.GenerateImageFromZip(static_image_dir)
472 else:
473 return self.GenerateLatestUpdateImage(board_id,
474 client_version,
475 static_image_dir)
476
477 def PreGenerateUpdate(self):
478 """Pre-generates an update. Does not work for factory or label updates."""
479 # Does not work with factory config.
480 assert(not self.factory_config)
481 _LogMessage('Pre-generating the update payload.')
482 # Does not work with labels so just use static dir.
483 if self.GenerateUpdatePayloadForNonFactory(self.static_dir):
484 # Force the devserver to use the pre-generated payload.
485 self.use_cached = True
486 else:
487 _LogMessage('Failed to pre-generate update.')
488
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700489 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700490 """Handles an update ping from an update client.
491
492 Args:
493 data: xml blob from client.
494 label: optional label for the update.
495 Returns:
496 Update payload message for client.
497 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700498 # Set hostname as the hostname that the client is calling to and set up
499 # the url base.
500 self.hostname = cherrypy.request.base
501 if self.urlbase:
502 static_urlbase = self.urlbase
503 elif self.serve_only:
504 static_urlbase = '%s/static/archive' % self.hostname
505 else:
506 static_urlbase = '%s/static' % self.hostname
507
508 _LogMessage('Using static url base %s' % static_urlbase)
509 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700510
511 # Check the client prefix to make sure you can support this type of update.
Chris Sosa9841e1c2010-10-14 10:51:45 -0700512 update_dom = minidom.parseString(data)
513 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700514 if (root.hasAttribute('updaterversion') and
515 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
Chris Sosa7c931362010-10-11 19:49:01 -0700516 _LogMessage('Got update from unsupported updater:' +
Chris Sosa0356d3b2010-09-16 15:46:22 -0700517 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700518 return self.GetNoUpdatePayload()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700519
520 # We only generate update payloads for updatecheck requests.
521 update_check = root.getElementsByTagName('o:updatecheck')
522 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700523 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700524 # TODO(sosa): Generate correct non-updatecheck payload to better test
525 # update clients.
526 return self.GetNoUpdatePayload()
527
528 # Since this is an updatecheck, get information about the requester.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700529 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800530 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700531 channel = query.getAttribute('track')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700532 board_id = (query.hasAttribute('board') and query.getAttribute('board')
533 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700534
Chris Sosa0356d3b2010-09-16 15:46:22 -0700535 # Separate logic as Factory requests have static url's that override
536 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700537 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700538 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700539 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700540 static_image_dir = self.static_dir
541 if label:
542 static_image_dir = os.path.join(static_image_dir, label)
543
Chris Sosa2c048f12010-10-27 16:05:27 -0700544 if self.GenerateUpdatePayloadForNonFactory(static_image_dir):
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700545 filename = os.path.join(static_image_dir, 'update.gz')
546 hash = self._GetHash(filename)
547 sha256 = self._GetSHA256(filename)
548 size = self._GetSize(filename)
549 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700550 if label:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700551 url = '%s/%s/update.gz' % (static_urlbase, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700552 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700553 url = '%s/update.gz' % static_urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700554
Chris Sosa7c931362010-10-11 19:49:01 -0700555 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700556 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700557 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700558 return self.GetNoUpdatePayload()