blob: c546018d9f70beeed87feae2041c8485eb74d936 [file] [log] [blame]
Chris Sosaaae36452010-09-15 17:06:05 -07001# Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
rtc@google.com64244662009-11-12 00:52:08 +00005from buildutil import BuildObject
rtc@google.comded22402009-10-26 22:36:21 +00006from xml.dom import minidom
7
8import os
Darin Petkov798fe7d2010-03-22 15:18:13 -07009import shutil
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070010import time
rtc@google.comded22402009-10-26 22:36:21 +000011import web
12
Chris Sosaaae36452010-09-15 17:06:05 -070013
rtc@google.com64244662009-11-12 00:52:08 +000014class Autoupdate(BuildObject):
Chris Sosaaae36452010-09-15 17:06:05 -070015 """Class that contains functionality that handles Chrome OS update pings.
16
17 Members:
18 serve_only: Serve images from a pre-built image.zip file. static_dir
19 must be set to the location of the image.zip.
20 factory_config: Path to the factory config file if handling factory
21 requests.
22 use_test_image: Use chromiumos_test_image.bin rather than the standard.
23 static_url_base: base URL, other than devserver, for update images.
24 client_prefix: The prefix for the update engine client.
25 forced_image: Path to an image to use for all updates.
26 """
rtc@google.comded22402009-10-26 22:36:21 +000027
Sean O'Connor1f7fd362010-04-07 16:34:52 -070028 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Chris Sosaaae36452010-09-15 17:06:05 -070029 factory_config_path=None, client_prefix=None, forced_image=None,
Sean O'Connor1f7fd362010-04-07 16:34:52 -070030 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070031 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070032 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -070033 self.factory_config = factory_config_path
Chris Sosaaae36452010-09-15 17:06:05 -070034 self.use_test_image = test_image
Sean O'Connor1f7fd362010-04-07 16:34:52 -070035 self.static_urlbase = urlbase
Chris Sosab63a9282010-09-02 10:43:23 -070036 self.client_prefix = client_prefix
Chris Sosaaae36452010-09-15 17:06:05 -070037 self.forced_image = forced_image
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070038
Chris Sosaaae36452010-09-15 17:06:05 -070039 def _GetSecondsSinceMidnight(self):
40 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070041 now = time.localtime()
42 return now[3] * 3600 + now[4] * 60 + now[5]
43
Chris Sosaaae36452010-09-15 17:06:05 -070044 def _GetDefaultBoardID(self):
45 """Returns the default board id stored in .default_board."""
46 board_file = '%s/.default_board' % (self.scripts_dir)
47 try:
48 return open(board_file).read()
49 except IOError:
50 return 'x86-generic'
51
52 def _GetLatestImageDir(self, board_id):
53 """Returns the latest image dir based on shell script."""
54 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
55 return os.popen(cmd).read().strip()
56
57 def _GetVersionFromDir(self, image_dir):
58 """Returns the version of the image based on the name of the directory."""
59 latest_version = os.path.basename(image_dir)
60 return latest_version.split('-')[0]
61
62 def _CanUpdate(self, client_version, latest_version):
63 """Returns true if the latest_version is greater than the client_version."""
64 client_tokens = client_version.replace('_', '').split('.')
65 latest_tokens = latest_version.replace('_', '').split('.')
66 web.debug('client version %s latest version %s'
67 % (client_version, latest_version))
68 for i in range(4):
69 if int(latest_tokens[i]) == int(client_tokens[i]):
70 continue
71 return int(latest_tokens[i]) > int(client_tokens[i])
72 return False
73
74 def _UnpackStatefulPartition(self, image_path, stateful_file):
75 """Given an image, unpacks its stateful partition to stateful_file."""
76 image_dir = os.path.dirname(image_path)
77 image_file = os.path.basename(image_path)
78
79 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
80 get_size = '$(cgpt show -s -i 1 %s)' % image_file
81 unpack_command = (
82 'cd %s && '
83 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
84 stateful_file, get_offset,
85 get_size))
86 web.debug(unpack_command)
87 return os.system(unpack_command) == 0
88
89 def _UnpackZip(self, image_dir):
90 """Unpacks an image.zip into a given directory."""
91 image = os.path.join(image_dir, self._GetImageName())
92 if os.path.exists(image):
93 return True
94 else:
95 # -n, never clobber an existing file, in case we get invoked
96 # simultaneously by multiple request handlers. This means that
97 # we're assuming each image.zip file lives in a versioned
98 # directory (a la Buildbot).
Chris Sosad83c2462010-09-15 17:43:12 -070099 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
Chris Sosaaae36452010-09-15 17:06:05 -0700100
101 def _GetImageName(self):
102 """Returns the name of the image that should be used."""
103 if self.use_test_image:
104 image_name = 'chromiumos_test_image.bin'
105 else:
106 image_name = 'chromiumos_image.bin'
107 return image_name
108
109 def _IsImageNewerThanCached(self, image_path, cached_file_path):
110 """Returns true if the image is newer than the cached image."""
Chris Sosad83c2462010-09-15 17:43:12 -0700111 if os.path.exists(cached_file_path) and os.path.exists(image_path):
112 web.debug('Usable cached image found.')
113 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
114 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path):
115 raise Exception('Image does not exist and cached image missing')
Chris Sosaaae36452010-09-15 17:06:05 -0700116 else:
Chris Sosad83c2462010-09-15 17:43:12 -0700117 # Only one is missing, figure out which one.
118 if os.path.exists(image_path):
119 web.debug('No cached image found - image generation required.')
120 return True
121 else:
122 web.debug('Only cached image found to serve.')
123 return False
Chris Sosaaae36452010-09-15 17:06:05 -0700124
125 def _GetSize(self, update_path):
126 """Returns the size of the file given."""
127 return os.path.getsize(update_path)
128
129 def _GetHash(self, update_path):
130 """Returns the sha1 of the file given."""
131 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
132 % update_path)
133 return os.popen(cmd).read().rstrip()
134
rtc@google.com21a5ca32009-11-04 18:23:23 +0000135 def GetUpdatePayload(self, hash, size, url):
Chris Sosaaae36452010-09-15 17:06:05 -0700136 """Returns a payload to the client corresponding to a new update.
137
138 Args:
139 hash: hash of update blob
140 size: size of update blob
141 url: where to find update blob
142 Returns:
143 Xml string to be passed back to client.
144 """
rtc@google.com21a5ca32009-11-04 18:23:23 +0000145 payload = """<?xml version="1.0" encoding="UTF-8"?>
146 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700147 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000148 <app appid="{%s}" status="ok">
149 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700150 <updatecheck
151 codebase="%s"
152 hash="%s"
153 needsadmin="false"
154 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +0000155 status="ok"/>
156 </app>
157 </gupdate>
158 """
Chris Sosaaae36452010-09-15 17:06:05 -0700159 return payload % (self._GetSecondsSinceMidnight(),
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700160 self.app_id, url, hash, size)
rtc@google.comded22402009-10-26 22:36:21 +0000161
rtc@google.com21a5ca32009-11-04 18:23:23 +0000162 def GetNoUpdatePayload(self):
Chris Sosaaae36452010-09-15 17:06:05 -0700163 """Returns a payload to the client corresponding to no update."""
164 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
165 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0" >
166 < daystart elapsed_seconds = "%s" />
167 < app appid = "{%s}" status = "ok" >
168 < ping status = "ok" />
169 < updatecheck status = "noupdate" />
170 </ app >
171 </ gupdate >
rtc@google.com21a5ca32009-11-04 18:23:23 +0000172 """
Chris Sosaaae36452010-09-15 17:06:05 -0700173 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000174
Chris Sosaaae36452010-09-15 17:06:05 -0700175 def GenerateUpdateFile(self, image_path):
176 """Generates an update gz given a full path to an image.
177
178 Args:
179 image_path: Full path to image.
180 Returns:
181 Path to created update_payload or None on error.
182 """
183 image_dir = os.path.dirname(image_path)
184 update_path = os.path.join(image_dir, 'update.gz')
185 web.debug('Generating update image %s' % update_path)
186
187 mkupdate_command = (
188 '%s/cros_generate_update_payload --image=%s --output=%s '
189 '--patch_kernel' % (self.scripts_dir, image_path, update_path))
190 if os.system(mkupdate_command) != 0:
191 web.debug('Failed to create base update file')
192 return None
193
194 return update_path
195
196 def GenerateStatefulFile(self, image_path):
197 """Generates a stateful update gz given a full path to an image.
198
199 Args:
200 image_path: Full path to image.
201 Returns:
202 Path to created stateful update_payload or None on error.
203 """
204 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
205
206 # Unpack to get stateful partition.
207 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
208 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
209 if os.system(mkstatefulupdate_command) == 0:
210 web.debug('Successfully generated %s.gz' % stateful_partition_path)
211 return '%s.gz' % stateful_partition_path
212
213 web.debug('Failed to create stateful update file')
214 return None
215
216 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
217 static_image_dir):
218 """Moves gz files from their directories to serving directories.
219
220 Args:
221 update_path: full path to main update gz.
222 stateful_update_path: full path to stateful partition gz.
223 static_image_dir: where to put files.
224 Returns:
225 Returns True if the files were moved over successfully.
226 """
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700227 try:
Chris Sosaaae36452010-09-15 17:06:05 -0700228 shutil.copy(update_path, static_image_dir)
229 shutil.copy(stateful_update_path, static_image_dir)
230 os.remove(update_path)
231 os.remove(stateful_update_path)
232 except Exception:
233 web.debug('Failed to move %s and %s to %s' % (update_path,
234 stateful_update_path,
235 static_image_dir))
236 return False
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700237
rtc@google.com21a5ca32009-11-04 18:23:23 +0000238 return True
rtc@google.comded22402009-10-26 22:36:21 +0000239
Chris Sosaaae36452010-09-15 17:06:05 -0700240 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
241 static_image_dir=None):
242 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000243
Chris Sosaaae36452010-09-15 17:06:05 -0700244 Args:
245 image_path: full path to the image.
246 move_to_static_dir: Moves the files from their dir to the static dir.
247 static_image_dir: the directory to move images to after generating.
248 Returns:
249 True if the update payload was created successfully.
250 """
251 web.debug('Generating update for image %s' % image_path)
252 update_path = self.GenerateUpdateFile(image_path)
253 stateful_update_path = self.GenerateStatefulFile(image_path)
254 if not update_path or not stateful_update_path:
255 web.debug('Failed to generate update')
256 return False
257
258 if move_to_static_dir:
259 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
260 static_image_dir)
261 else:
262 return True
263
264 def GenerateLatestUpdateImage(self, board_id, client_version,
265 static_image_dir=None):
266 """Generates an update using the latest image that has been built.
267
268 This will only generate an update if the newest update is newer than that
269 on the client or client_version is 'ForcedUpdate'.
270
271 Args:
272 board_id: Name of the board.
273 client_version: Current version of the client or 'ForcedUpdate'
274 static_image_dir: the directory to move images to after generating.
275 Returns:
276 True if the update payload was created successfully.
277 """
278 latest_image_dir = self._GetLatestImageDir(board_id)
279 latest_version = self._GetVersionFromDir(latest_image_dir)
280 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
281
282 web.debug('Preparing to generate update from latest built image %s.' %
283 latest_image_path)
284
285 # Check to see whether or not we should update.
286 if client_version != 'ForcedUpdate' and not self._CanUpdate(
287 client_version, latest_version):
288 web.debug('no update')
289 return False
290
291 cached_file_path = os.path.join(static_image_dir, 'update.gz')
292 if (os.path.exists(cached_file_path) and
293 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
294 return True
295
296 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
297 static_image_dir=static_image_dir)
298
299 def GenerateImageFromZip(self, static_image_dir):
300 """Generates an update from an image zip file.
301
302 This method assumes you have an image.zip in directory you are serving
303 from. If this file is newer than a previously cached file, it will unzip
304 this file, create a payload and serve it.
305
306 Args:
307 static_image_dir: Directory where the zip file exists.
308 Returns:
309 True if the update payload was created successfully.
310 """
311 web.debug('Preparing to generate update from zip in %s.' % static_image_dir)
312 image_path = os.path.join(static_image_dir, self._GetImageName())
313 cached_file_path = os.path.join(static_image_dir, 'update.gz')
Chris Sosad83c2462010-09-15 17:43:12 -0700314 zip_file_path = os.path.join(static_image_dir, 'image.zip')
315 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
Chris Sosaaae36452010-09-15 17:06:05 -0700316 return True
317
Chris Sosad83c2462010-09-15 17:43:12 -0700318 if not self._UnpackZip(static_image_dir):
Chris Sosaaae36452010-09-15 17:06:05 -0700319 web.debug('unzip image.zip failed.')
320 return False
321
322 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
323 static_image_dir=None)
Darin Petkov8ef83452010-03-23 16:52:29 -0700324
Andrew de los Reyes52620802010-04-12 13:40:07 -0700325 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
326 """Imports a factory-floor server configuration file. The file should
327 be in this format:
328 config = [
329 {
330 'qual_ids': set([1, 2, 3, "x86-generic"]),
331 'factory_image': 'generic-factory.gz',
332 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
333 'release_image': 'generic-release.gz',
334 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
335 'oempartitionimg_image': 'generic-oem.gz',
336 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700337 'efipartitionimg_image': 'generic-efi.gz',
338 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700339 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800340 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800341 'firmware_image': 'generic-firmware.gz',
342 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700343 },
344 {
345 'qual_ids': set([6]),
346 'factory_image': '6-factory.gz',
347 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
348 'release_image': '6-release.gz',
349 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
350 'oempartitionimg_image': '6-oem.gz',
351 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700352 'efipartitionimg_image': '6-efi.gz',
353 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700354 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800355 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800356 'firmware_image': '6-firmware.gz',
357 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700358 },
359 ]
360 The server will look for the files by name in the static files
361 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700362
Andrew de los Reyes52620802010-04-12 13:40:07 -0700363 If validate_checksums is True, validates checksums and exits. If
364 a checksum mismatch is found, it's printed to the screen.
365 """
366 f = open(filename, 'r')
367 output = {}
368 exec(f.read(), output)
369 self.factory_config = output['config']
370 success = True
371 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800372 for key in stanza.copy().iterkeys():
373 suffix = '_image'
374 if key.endswith(suffix):
375 kind = key[:-len(suffix)]
Chris Sosaaae36452010-09-15 17:06:05 -0700376 stanza[kind + '_size'] = os.path.getsize(os.path.join(
377 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800378 if validate_checksums:
Chris Sosaaae36452010-09-15 17:06:05 -0700379 factory_checksum = self._GetHash(self.static_dir + ' / ' +
380 stanza[kind + '_image'])
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800381 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosaaae36452010-09-15 17:06:05 -0700382 print ('Error: checksum mismatch for %s. Expected "%s" but file '
383 'has checksum "%s".' % (stanza[kind + '_image'],
384 stanza[kind + '_checksum'],
385 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800386 success = False
Chris Sosaaae36452010-09-15 17:06:05 -0700387
Andrew de los Reyes52620802010-04-12 13:40:07 -0700388 if validate_checksums:
389 if success is False:
390 raise Exception('Checksum mismatch in conf file.')
Chris Sosaaae36452010-09-15 17:06:05 -0700391
Andrew de los Reyes52620802010-04-12 13:40:07 -0700392 print 'Config file looks good.'
393
394 def GetFactoryImage(self, board_id, channel):
Chris Sosaaae36452010-09-15 17:06:05 -0700395 kind = channel.rsplit(' - ', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700396 for stanza in self.factory_config:
397 if board_id not in stanza['qual_ids']:
398 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700399 if kind + '_image' not in stanza:
400 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700401 return (stanza[kind + '_image'],
402 stanza[kind + '_checksum'],
403 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700404 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000405
Chris Sosaaae36452010-09-15 17:06:05 -0700406 def HandleFactoryRequest(self, hostname, board_id, channel):
407 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
408 if filename is None:
409 web.debug('unable to find image for board %s' % board_id)
410 return self.GetNoUpdatePayload()
411 url = 'http://%s/static/%s' % (hostname, filename)
412 web.debug('returning update payload ' + url)
413 return self.GetUpdatePayload(checksum, size, url)
414
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700415 def HandleUpdatePing(self, data, label=None):
Chris Sosaaae36452010-09-15 17:06:05 -0700416 """Handles an update ping from an update client.
417
418 Args:
419 data: xml blob from client.
420 label: optional label for the update.
421 Returns:
422 Update payload message for client.
423 """
424 web.debug('handling update ping: %s' % data)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000425 update_dom = minidom.parseString(data)
426 root = update_dom.firstChild
Chris Sosaaae36452010-09-15 17:06:05 -0700427
428 # Check the client prefix to make sure you can support this type of update.
429 if (root.hasAttribute('updaterversion') and
430 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
431 web.debug('Got update from unsupported updater:' +
432 root.getAttribute('updaterversion'))
Andrew de los Reyes9223f132010-05-07 17:08:17 -0700433 return self.GetNoUpdatePayload()
Chris Sosaaae36452010-09-15 17:06:05 -0700434
435 # We only generate update payloads for updatecheck requests.
436 update_check = root.getElementsByTagName('o:updatecheck')
437 if not update_check:
438 web.debug('Non-update check received. Returning blank payload.')
439 # TODO(sosa): Generate correct non-updatecheck payload to better test
440 # update clients.
441 return self.GetNoUpdatePayload()
442
443 # Since this is an updatecheck, get information about the requester.
444 hostname = web.ctx.host
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700445 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800446 client_version = query.getAttribute('version')
Andrew de los Reyes52620802010-04-12 13:40:07 -0700447 channel = query.getAttribute('track')
Chris Sosaaae36452010-09-15 17:06:05 -0700448 board_id = (query.hasAttribute('board') and query.getAttribute('board')
449 or self._GetDefaultBoardID())
Andrew de los Reyes52620802010-04-12 13:40:07 -0700450
Chris Sosaaae36452010-09-15 17:06:05 -0700451 # Separate logic as Factory requests have static url's that override
452 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700453 if self.factory_config:
Chris Sosaaae36452010-09-15 17:06:05 -0700454 return self.HandleFactoryRequest(hostname, board_id, channel)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700455 else:
Chris Sosaaae36452010-09-15 17:06:05 -0700456 static_image_dir = self.static_dir
457 if label:
458 static_image_dir = os.path.join(static_image_dir, label)
459
460 # Not for factory, find and serve the correct image given the options.
461 if self.forced_image:
462 has_built_image = self.GenerateUpdateImage(
463 self.forced_image, move_to_static_dir=True,
464 static_image_dir=static_image_dir)
465 # Now that we've generated it, clear out so that other pings of same
466 # devserver instance do not generate new images.
467 self.forced_image = None
468 elif self.serve_only:
469 has_built_image = self.GenerateImageFromZip(static_image_dir)
470 else:
471 has_built_image = self.GenerateLatestUpdateImage(board_id,
472 client_version,
473 static_image_dir)
474
475 if has_built_image:
476 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz'))
477 size = self._GetSize(os.path.join(static_image_dir, 'update.gz'))
478 if self.static_urlbase and label:
479 url = '%s/%s/update.gz' % (self.static_urlbase, label)
480 elif self.serve_only:
481 url = 'http://%s/static/archive/update.gz' % hostname
482 else:
483 url = 'http://%s/static/update.gz' % hostname
484 return self.GetUpdatePayload(hash, size, url)
485 else:
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700486 return self.GetNoUpdatePayload()