Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(436)

Side by Side Diff: autoupdate.py

Issue 3459002: Revert devserver update to fix factory install. (Closed) Base URL: http://git.chromium.org/git/dev-util.git
Patch Set: Created 10 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | autoupdate_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2009-2010 The Chromium OS Authors. All rights reserved. 1 # Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from buildutil import BuildObject 5 from buildutil import BuildObject
6 from xml.dom import minidom 6 from xml.dom import minidom
7 7
8 import os 8 import os
9 import shutil 9 import shutil
10 import sys
10 import time 11 import time
11 import web 12 import web
12 13
13
14 class Autoupdate(BuildObject): 14 class Autoupdate(BuildObject):
15 """Class that contains functionality that handles Chrome OS update pings. 15 # Basic functionality of handling ChromeOS autoupdate pings
16 16 # and building/serving update images.
17 Members: 17 # TODO(rtc): Clean this code up and write some tests.
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 """
27 18
28 def __init__(self, serve_only=None, test_image=False, urlbase=None, 19 def __init__(self, serve_only=None, test_image=False, urlbase=None,
29 factory_config_path=None, client_prefix=None, forced_image=None, 20 factory_config_path=None, validate_factory_config=None,
21 client_prefix=None,
30 *args, **kwargs): 22 *args, **kwargs):
31 super(Autoupdate, self).__init__(*args, **kwargs) 23 super(Autoupdate, self).__init__(*args, **kwargs)
32 self.serve_only = serve_only 24 self.serve_only = serve_only
33 self.factory_config = factory_config_path 25 self.factory_config = factory_config_path
34 self.use_test_image = test_image 26 self.test_image = test_image
35 self.static_urlbase = urlbase 27 self.static_urlbase = urlbase
36 self.client_prefix = client_prefix 28 self.client_prefix = client_prefix
37 self.forced_image = forced_image 29 if serve_only:
30 # If we're serving out of an archived build dir (e.g. a
31 # buildbot), prepare this webserver's magic 'static/' dir with a
32 # link to the build archive.
33 web.debug('Autoupdate in "serve update images only" mode.')
34 if os.path.exists('static/archive'):
35 if self.static_dir != os.readlink('static/archive'):
36 web.debug('removing stale symlink to %s' % self.static_dir)
37 os.unlink('static/archive')
38 os.symlink(self.static_dir, 'static/archive')
39 else:
40 os.symlink(self.static_dir, 'static/archive')
41 if factory_config_path is not None:
42 self.ImportFactoryConfigFile(factory_config_path, validate_factory_config)
38 43
39 def _GetSecondsSinceMidnight(self): 44 def GetSecondsSinceMidnight(self):
40 """Returns the seconds since midnight as a decimal value."""
41 now = time.localtime() 45 now = time.localtime()
42 return now[3] * 3600 + now[4] * 60 + now[5] 46 return now[3] * 3600 + now[4] * 60 + now[5]
43 47
44 def _GetDefaultBoardID(self): 48 def GetUpdatePayload(self, hash, size, url):
45 """Returns the default board id stored in .default_board.""" 49 payload = """<?xml version="1.0" encoding="UTF-8"?>
50 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
51 <daystart elapsed_seconds="%s"/>
52 <app appid="{%s}" status="ok">
53 <ping status="ok"/>
54 <updatecheck
55 codebase="%s"
56 hash="%s"
57 needsadmin="false"
58 size="%s"
59 status="ok"/>
60 </app>
61 </gupdate>
62 """
63 return payload % (self.GetSecondsSinceMidnight(),
64 self.app_id, url, hash, size)
65
66 def GetNoUpdatePayload(self):
67 payload = """<?xml version="1.0" encoding="UTF-8"?>
68 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
69 <daystart elapsed_seconds="%s"/>
70 <app appid="{%s}" status="ok">
71 <ping status="ok"/>
72 <updatecheck status="noupdate"/>
73 </app>
74 </gupdate>
75 """
76 return payload % (self.GetSecondsSinceMidnight(), self.app_id)
77
78 def GetDefaultBoardID(self):
46 board_file = '%s/.default_board' % (self.scripts_dir) 79 board_file = '%s/.default_board' % (self.scripts_dir)
47 try: 80 try:
48 return open(board_file).read() 81 return open(board_file).read()
49 except IOError: 82 except IOError:
50 return 'x86-generic' 83 return 'x86-generic'
51 84
52 def _GetLatestImageDir(self, board_id): 85 def GetLatestImagePath(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) 86 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
55 return os.popen(cmd).read().strip() 87 return os.popen(cmd).read().strip()
56 88
57 def _GetVersionFromDir(self, image_dir): 89 def GetLatestVersion(self, latest_image_path):
58 """Returns the version of the image based on the name of the directory.""" 90 latest_version = latest_image_path.split('/')[-1]
59 latest_version = os.path.basename(image_dir) 91
92 # Removes the portage build prefix.
93 latest_version = latest_version.lstrip('g-')
60 return latest_version.split('-')[0] 94 return latest_version.split('-')[0]
61 95
62 def _CanUpdate(self, client_version, latest_version): 96 def CanUpdate(self, client_version, latest_version):
63 """Returns true if the latest_version is greater than the client_version.""" 97 """
98 Returns true iff the latest_version is greater than the client_version.
99 """
64 client_tokens = client_version.replace('_', '').split('.') 100 client_tokens = client_version.replace('_', '').split('.')
65 latest_tokens = latest_version.replace('_', '').split('.') 101 latest_tokens = latest_version.replace('_', '').split('.')
66 web.debug('client version %s latest version %s' 102 web.debug('client version %s latest version %s' \
67 % (client_version, latest_version)) 103 % (client_version, latest_version))
68 for i in range(4): 104 for i in range(4):
69 if int(latest_tokens[i]) == int(client_tokens[i]): 105 if int(latest_tokens[i]) == int(client_tokens[i]):
70 continue 106 continue
71 return int(latest_tokens[i]) > int(client_tokens[i]) 107 return int(latest_tokens[i]) > int(client_tokens[i])
72 return False 108 return False
73 109
74 def _UnpackStatefulPartition(self, image_path, stateful_file): 110 def UnpackStatefulPartition(self, image_path, image_file, stateful_file):
75 """Given an image, unpacks its stateful partition to stateful_file.""" 111 """Given an image, unpacks the 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 112 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
80 get_size = '$(cgpt show -s -i 1 %s)' % image_file 113 get_size = '$(cgpt show -s -i 1 %s)' % image_file
81 unpack_command = ( 114 unpack_command = (
82 'cd %s && ' 115 'cd %s && '
83 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file, 116 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_path, image_file,
84 stateful_file, get_offset, 117 stateful_file, get_offset, get_size))
85 get_size))
86 web.debug(unpack_command) 118 web.debug(unpack_command)
87 return os.system(unpack_command) == 0 119 return os.system(unpack_command) == 0
88 120
89 def _UnpackZip(self, image_dir): 121 def UnpackZip(self, image_path, image_file):
90 """Unpacks an image.zip into a given directory.""" 122 image = os.path.join(image_path, image_file)
91 image = os.path.join(image_dir, self._GetImageName())
92 if os.path.exists(image): 123 if os.path.exists(image):
93 return True 124 return True
94 else: 125 else:
95 # -n, never clobber an existing file, in case we get invoked 126 # -n, never clobber an existing file, in case we get invoked
96 # simultaneously by multiple request handlers. This means that 127 # simultaneously by multiple request handlers. This means that
97 # we're assuming each image.zip file lives in a versioned 128 # we're assuming each image.zip file lives in a versioned
98 # directory (a la Buildbot). 129 # directory (a la Buildbot).
99 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0 130 return os.system('cd %s && unzip -n image.zip %s unpack_partitions.sh' %
131 (image_path, image_file)) == 0
100 132
101 def _GetImageName(self): 133 def GetImageBinPath(self, image_path):
102 """Returns the name of the image that should be used.""" 134 if self.test_image:
103 if self.use_test_image: 135 image_file = 'chromiumos_test_image.bin'
104 image_name = 'chromiumos_test_image.bin'
105 else: 136 else:
106 image_name = 'chromiumos_image.bin' 137 image_file = 'chromiumos_image.bin'
107 return image_name 138 return image_file
108 139
109 def _IsImageNewerThanCached(self, image_path, cached_file_path): 140 def BuildUpdateImage(self, image_path):
110 """Returns true if the image is newer than the cached image.""" 141 stateful_file = '%s/stateful.image' % image_path
111 if os.path.exists(cached_file_path) and os.path.exists(image_path): 142 image_file = self.GetImageBinPath(image_path)
112 web.debug('Usable cached image found.') 143 bin_path = os.path.join(image_path, image_file)
113 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path) 144
114 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path) : 145 # Get appropriate update.gz to compare timestamps.
115 raise Exception('Image does not exist and cached image missing') 146 if self.serve_only:
147 cached_update_file = os.path.join(image_path, 'update.gz')
116 else: 148 else:
117 # Only one is missing, figure out which one. 149 cached_update_file = os.path.join(self.static_dir, 'update.gz')
118 if os.path.exists(image_path): 150
119 web.debug('No cached image found - image generation required.') 151 # If the new chromiumos image is newer, re-create everything.
120 return True 152 if (os.path.exists(cached_update_file) and
121 else: 153 os.path.getmtime(cached_update_file) >= os.path.getmtime(bin_path)):
122 web.debug('Only cached image found to serve.') 154 web.debug('Using cached update image at %s instead of %s' %
155 (cached_update_file, bin_path))
156 else:
157 # Unpack zip file if we are serving from a directory.
158 if self.serve_only and not self.UnpackZip(image_path, image_file):
159 web.debug('unzip image.zip failed.')
123 return False 160 return False
124 161
125 def _GetSize(self, update_path): 162 update_file = os.path.join(image_path, 'update.gz')
126 """Returns the size of the file given.""" 163 web.debug('Generating update image %s' % update_file)
164 mkupdate_command = (
165 '%s/cros_generate_update_payload --image=%s --output=%s '
166 '--patch_kernel' % (self.scripts_dir, bin_path, update_file))
167 if os.system(mkupdate_command) != 0:
168 web.debug('Failed to create update image')
169 return False
170
171 # Unpack to get stateful partition.
172 if not self.UnpackStatefulPartition(image_path, image_file,
173 stateful_file):
174 web.debug('Failed to unpack stateful partition.')
175 return False
176
177 mkstatefulupdate_command = 'gzip -f %s' % stateful_file
178 if os.system(mkstatefulupdate_command) != 0:
179 web.debug('Failed to create stateful update gz')
180 return False
181
182 # Add gz suffix
183 stateful_file = '%s.gz' % stateful_file
184
185 # Cleanup of image files.
186 if not self.serve_only:
187 try:
188 web.debug('Found a new image to serve, copying it to static')
189 shutil.copy(update_file, self.static_dir)
190 shutil.copy(stateful_file, self.static_dir)
191 os.remove(update_file)
192 os.remove(stateful_file)
193 except Exception, e:
194 web.debug('%s' % e)
195 return False
196 return True
197
198 def GetSize(self, update_path):
127 return os.path.getsize(update_path) 199 return os.path.getsize(update_path)
128 200
129 def _GetHash(self, update_path): 201 def GetHash(self, update_path):
130 """Returns the sha1 of the file given.""" 202 cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \
131 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';' 203 % update_path
132 % update_path)
133 return os.popen(cmd).read().rstrip() 204 return os.popen(cmd).read().rstrip()
134 205
135 def GetUpdatePayload(self, hash, size, url):
136 """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 """
145 payload = """<?xml version="1.0" encoding="UTF-8"?>
146 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
147 <daystart elapsed_seconds="%s"/>
148 <app appid="{%s}" status="ok">
149 <ping status="ok"/>
150 <updatecheck
151 codebase="%s"
152 hash="%s"
153 needsadmin="false"
154 size="%s"
155 status="ok"/>
156 </app>
157 </gupdate>
158 """
159 return payload % (self._GetSecondsSinceMidnight(),
160 self.app_id, url, hash, size)
161
162 def GetNoUpdatePayload(self):
163 """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 >
172 """
173 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
174
175 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 """
227 try:
228 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
237
238 return True
239
240 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.
243
244 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')
314 zip_file_path = os.path.join(static_image_dir, 'image.zip')
315 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path):
316 return True
317
318 if not self._UnpackZip(static_image_dir):
319 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)
324
325 def ImportFactoryConfigFile(self, filename, validate_checksums=False): 206 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
326 """Imports a factory-floor server configuration file. The file should 207 """Imports a factory-floor server configuration file. The file should
327 be in this format: 208 be in this format:
328 config = [ 209 config = [
329 { 210 {
330 'qual_ids': set([1, 2, 3, "x86-generic"]), 211 'qual_ids': set([1, 2, 3, "x86-generic"]),
331 'factory_image': 'generic-factory.gz', 212 'factory_image': 'generic-factory.gz',
332 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', 213 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
333 'release_image': 'generic-release.gz', 214 'release_image': 'generic-release.gz',
334 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', 215 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
366 f = open(filename, 'r') 247 f = open(filename, 'r')
367 output = {} 248 output = {}
368 exec(f.read(), output) 249 exec(f.read(), output)
369 self.factory_config = output['config'] 250 self.factory_config = output['config']
370 success = True 251 success = True
371 for stanza in self.factory_config: 252 for stanza in self.factory_config:
372 for key in stanza.copy().iterkeys(): 253 for key in stanza.copy().iterkeys():
373 suffix = '_image' 254 suffix = '_image'
374 if key.endswith(suffix): 255 if key.endswith(suffix):
375 kind = key[:-len(suffix)] 256 kind = key[:-len(suffix)]
376 stanza[kind + '_size'] = os.path.getsize(os.path.join( 257 stanza[kind + '_size'] = \
377 self.static_dir, stanza[kind + '_image'])) 258 os.path.getsize(self.static_dir + '/' + stanza[kind + '_image'])
378 if validate_checksums: 259 if validate_checksums:
379 factory_checksum = self._GetHash(self.static_dir + ' / ' + 260 factory_checksum = self.GetHash(self.static_dir + '/' +
380 stanza[kind + '_image']) 261 stanza[kind + '_image'])
381 if factory_checksum != stanza[kind + '_checksum']: 262 if factory_checksum != stanza[kind + '_checksum']:
382 print ('Error: checksum mismatch for %s. Expected "%s" but file ' 263 print 'Error: checksum mismatch for %s. Expected "%s" but file ' \
383 'has checksum "%s".' % (stanza[kind + '_image'], 264 'has checksum "%s".' % (stanza[kind + '_image'],
384 stanza[kind + '_checksum'], 265 stanza[kind + '_checksum'],
385 factory_checksum)) 266 factory_checksum)
386 success = False 267 success = False
387
388 if validate_checksums: 268 if validate_checksums:
389 if success is False: 269 if success is False:
390 raise Exception('Checksum mismatch in conf file.') 270 raise Exception('Checksum mismatch in conf file.')
391
392 print 'Config file looks good.' 271 print 'Config file looks good.'
393 272
394 def GetFactoryImage(self, board_id, channel): 273 def GetFactoryImage(self, board_id, channel):
395 kind = channel.rsplit(' - ', 1)[0] 274 kind = channel.rsplit('-', 1)[0]
396 for stanza in self.factory_config: 275 for stanza in self.factory_config:
397 if board_id not in stanza['qual_ids']: 276 if board_id not in stanza['qual_ids']:
398 continue 277 continue
399 if kind + '_image' not in stanza: 278 if kind + '_image' not in stanza:
400 break 279 break
401 return (stanza[kind + '_image'], 280 return (stanza[kind + '_image'],
402 stanza[kind + '_checksum'], 281 stanza[kind + '_checksum'],
403 stanza[kind + '_size']) 282 stanza[kind + '_size'])
404 return (None, None, None) 283 return (None, None, None)
405 284
406 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
415 def HandleUpdatePing(self, data, label=None): 285 def HandleUpdatePing(self, data, label=None):
416 """Handles an update ping from an update client. 286 web.debug('handle update ping: %s' % data)
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)
425 update_dom = minidom.parseString(data) 287 update_dom = minidom.parseString(data)
426 root = update_dom.firstChild 288 root = update_dom.firstChild
427 289 if root.hasAttribute('updaterversion') and \
428 # Check the client prefix to make sure you can support this type of update. 290 not root.getAttribute('updaterversion').startswith(
429 if (root.hasAttribute('updaterversion') and 291 self.client_prefix):
430 not root.getAttribute('updaterversion').startswith(self.client_prefix)): 292 web.debug('Got update from unsupported updater:' + \
431 web.debug('Got update from unsupported updater:' + 293 root.getAttribute('updaterversion'))
432 root.getAttribute('updaterversion'))
433 return self.GetNoUpdatePayload() 294 return self.GetNoUpdatePayload()
434
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
445 query = root.getElementsByTagName('o:app')[0] 295 query = root.getElementsByTagName('o:app')[0]
446 client_version = query.getAttribute('version') 296 client_version = query.getAttribute('version')
447 channel = query.getAttribute('track') 297 channel = query.getAttribute('track')
448 board_id = (query.hasAttribute('board') and query.getAttribute('board') 298 board_id = query.hasAttribute('board') and query.getAttribute('board') \
449 or self._GetDefaultBoardID()) 299 or self.GetDefaultBoardID()
300 latest_image_path = self.GetLatestImagePath(board_id)
301 latest_version = self.GetLatestVersion(latest_image_path)
302 hostname = web.ctx.host
450 303
451 # Separate logic as Factory requests have static url's that override 304 # If this is a factory floor server, return the image here:
452 # other options.
453 if self.factory_config: 305 if self.factory_config:
454 return self.HandleFactoryRequest(hostname, board_id, channel) 306 (filename, checksum, size) = \
307 self.GetFactoryImage(board_id, channel)
308 if filename is None:
309 web.debug('unable to find image for board %s' % board_id)
310 return self.GetNoUpdatePayload()
311 url = 'http://%s/static/%s' % (hostname, filename)
312 web.debug('returning update payload ' + url)
313 return self.GetUpdatePayload(checksum, size, url)
314
315 if client_version != 'ForcedUpdate' \
316 and not self.CanUpdate(client_version, latest_version):
317 web.debug('no update')
318 return self.GetNoUpdatePayload()
319 if label:
320 web.debug('Client requested version %s' % label)
321 # Check that matching build exists
322 image_path = '%s/%s' % (self.static_dir, label)
323 if not os.path.exists(image_path):
324 web.debug('%s not found.' % image_path)
325 return self.GetNoUpdatePayload()
326 # Construct a response
327 ok = self.BuildUpdateImage(image_path)
328 if ok != True:
329 web.debug('Failed to build an update image')
330 return self.GetNoUpdatePayload()
331 web.debug('serving update: ')
332 hash = self.GetHash('%s/%s/update.gz' % (self.static_dir, label))
333 size = self.GetSize('%s/%s/update.gz' % (self.static_dir, label))
334 # In case we configured images to be hosted elsewhere
335 # (e.g. buildbot's httpd), use that. Otherwise, serve it
336 # ourselves using web.py's static resource handler.
337 if self.static_urlbase:
338 urlbase = self.static_urlbase
339 else:
340 urlbase = 'http://%s/static/archive/' % hostname
341
342 url = '%s/%s/update.gz' % (urlbase, label)
343 return self.GetUpdatePayload(hash, size, url)
344 web.debug('DONE')
455 else: 345 else:
456 static_image_dir = self.static_dir 346 web.debug('update found %s ' % latest_version)
457 if label: 347 ok = self.BuildUpdateImage(latest_image_path)
458 static_image_dir = os.path.join(static_image_dir, label) 348 if ok != True:
349 web.debug('Failed to build an update image')
350 return self.GetNoUpdatePayload()
459 351
460 # Not for factory, find and serve the correct image given the options. 352 hash = self.GetHash('%s/update.gz' % self.static_dir)
461 if self.forced_image: 353 size = self.GetSize('%s/update.gz' % self.static_dir)
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 354
475 if has_built_image: 355 url = 'http://%s/static/update.gz' % hostname
476 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz')) 356 return self.GetUpdatePayload(hash, size, url)
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:
486 return self.GetNoUpdatePayload()
OLDNEW
« no previous file with comments | « no previous file | autoupdate_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698