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

Side by Side Diff: autoupdate.py

Issue 3296022: Re-write of autoupdate.py to merge changes to be more cohesive and allow a forced_image option. (Closed) Base URL: http://git.chromium.org/git/dev-util.git
Patch Set: Cleanup before push. 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 The Chromium OS Authors. All rights reserved. 1 # Copyright (c) 2009-2010 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
11 import time 10 import time
12 import web 11 import web
13 12
13
14 class Autoupdate(BuildObject): 14 class Autoupdate(BuildObject):
15 # Basic functionality of handling ChromeOS autoupdate pings 15 """Class that contains functionality that handles Chrome OS update pings.
16 # and building/serving update images. 16
17 # TODO(rtc): Clean this code up and write some tests. 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 """
18 27
19 def __init__(self, serve_only=None, test_image=False, urlbase=None, 28 def __init__(self, serve_only=None, test_image=False, urlbase=None,
20 factory_config_path=None, validate_factory_config=None, 29 factory_config_path=None, client_prefix=None, forced_image=None,
21 client_prefix=None,
22 *args, **kwargs): 30 *args, **kwargs):
23 super(Autoupdate, self).__init__(*args, **kwargs) 31 super(Autoupdate, self).__init__(*args, **kwargs)
24 self.serve_only = serve_only 32 self.serve_only = serve_only
25 self.factory_config = factory_config_path 33 self.factory_config = factory_config_path
26 self.test_image = test_image 34 self.use_test_image = test_image
27 self.static_urlbase = urlbase 35 self.static_urlbase = urlbase
28 self.client_prefix = client_prefix 36 self.client_prefix = client_prefix
29 if serve_only: 37 self.forced_image = forced_image
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)
43 38
44 def GetSecondsSinceMidnight(self): 39 def _GetSecondsSinceMidnight(self):
40 """Returns the seconds since midnight as a decimal value."""
45 now = time.localtime() 41 now = time.localtime()
46 return now[3] * 3600 + now[4] * 60 + now[5] 42 return now[3] * 3600 + now[4] * 60 + now[5]
47 43
48 def GetUpdatePayload(self, hash, size, url): 44 def _GetDefaultBoardID(self):
49 payload = """<?xml version="1.0" encoding="UTF-8"?> 45 """Returns the default board id stored in .default_board."""
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):
79 board_file = '%s/.default_board' % (self.scripts_dir) 46 board_file = '%s/.default_board' % (self.scripts_dir)
80 try: 47 try:
81 return open(board_file).read() 48 return open(board_file).read()
82 except IOError: 49 except IOError:
83 return 'x86-generic' 50 return 'x86-generic'
84 51
85 def GetLatestImagePath(self, board_id): 52 def _GetLatestImageDir(self, board_id):
53 """Returns the latest image dir based on shell script."""
86 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id) 54 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
87 return os.popen(cmd).read().strip() 55 return os.popen(cmd).read().strip()
88 56
89 def GetLatestVersion(self, latest_image_path): 57 def _GetVersionFromDir(self, image_dir):
90 latest_version = latest_image_path.split('/')[-1] 58 """Returns the version of the image based on the name of the directory."""
91 59 latest_version = os.path.basename(image_dir)
92 # Removes the portage build prefix.
93 latest_version = latest_version.lstrip('g-')
94 return latest_version.split('-')[0] 60 return latest_version.split('-')[0]
95 61
96 def CanUpdate(self, client_version, latest_version): 62 def _CanUpdate(self, client_version, latest_version):
97 """ 63 """Returns true if the latest_version is greater than the client_version."""
98 Returns true iff the latest_version is greater than the client_version.
99 """
100 client_tokens = client_version.replace('_', '').split('.') 64 client_tokens = client_version.replace('_', '').split('.')
101 latest_tokens = latest_version.replace('_', '').split('.') 65 latest_tokens = latest_version.replace('_', '').split('.')
102 web.debug('client version %s latest version %s' \ 66 web.debug('client version %s latest version %s'
103 % (client_version, latest_version)) 67 % (client_version, latest_version))
104 for i in range(4): 68 for i in range(4):
105 if int(latest_tokens[i]) == int(client_tokens[i]): 69 if int(latest_tokens[i]) == int(client_tokens[i]):
106 continue 70 continue
107 return int(latest_tokens[i]) > int(client_tokens[i]) 71 return int(latest_tokens[i]) > int(client_tokens[i])
108 return False 72 return False
109 73
110 def UnpackStatefulPartition(self, image_path, image_file, stateful_file): 74 def _UnpackStatefulPartition(self, image_path, stateful_file):
111 """Given an image, unpacks the stateful partition to 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
112 get_offset = '$(cgpt show -b -i 1 %s)' % image_file 79 get_offset = '$(cgpt show -b -i 1 %s)' % image_file
113 get_size = '$(cgpt show -s -i 1 %s)' % image_file 80 get_size = '$(cgpt show -s -i 1 %s)' % image_file
114 unpack_command = ( 81 unpack_command = (
115 'cd %s && ' 82 'cd %s && '
116 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_path, image_file, 83 'dd if=%s of=%s bs=512 skip=%s count=%s' % (image_dir, image_file,
117 stateful_file, get_offset, get_size)) 84 stateful_file, get_offset,
85 get_size))
118 web.debug(unpack_command) 86 web.debug(unpack_command)
119 return os.system(unpack_command) == 0 87 return os.system(unpack_command) == 0
120 88
121 def UnpackZip(self, image_path, image_file): 89 def _UnpackZip(self, image_dir):
122 image = os.path.join(image_path, image_file) 90 """Unpacks an image.zip into a given directory."""
91 image = os.path.join(image_dir, self._GetImageName())
123 if os.path.exists(image): 92 if os.path.exists(image):
124 return True 93 return True
125 else: 94 else:
126 # -n, never clobber an existing file, in case we get invoked 95 # -n, never clobber an existing file, in case we get invoked
127 # simultaneously by multiple request handlers. This means that 96 # simultaneously by multiple request handlers. This means that
128 # we're assuming each image.zip file lives in a versioned 97 # we're assuming each image.zip file lives in a versioned
129 # directory (a la Buildbot). 98 # directory (a la Buildbot).
130 return os.system('cd %s && unzip -n image.zip %s unpack_partitions.sh' % 99 return os.system('cd %s && unzip -n image.zip %s unpack_partitions.sh' %
131 (image_path, image_file)) == 0 100 (image_dir, self._GetImageName())) == 0
132 101
133 def GetImageBinPath(self, image_path): 102 def _GetImageName(self):
134 if self.test_image: 103 """Returns the name of the image that should be used."""
135 image_file = 'chromiumos_test_image.bin' 104 if self.use_test_image:
105 image_name = 'chromiumos_test_image.bin'
136 else: 106 else:
137 image_file = 'chromiumos_image.bin' 107 image_name = 'chromiumos_image.bin'
138 return image_file 108 return image_name
139 109
140 def BuildUpdateImage(self, image_path): 110 def _IsImageNewerThanCached(self, image_path, cached_file_path):
141 stateful_file = '%s/stateful.image' % image_path 111 """Returns true if the image is newer than the cached image."""
142 image_file = self.GetImageBinPath(image_path) 112 # No image to compare against.
143 bin_path = os.path.join(image_path, image_file) 113 if not os.path.exists(image_path) and os.path.exists(cached_file_path):
144 114 return True
145 # Get appropriate update.gz to compare timestamps. 115
146 if self.serve_only: 116 if (os.path.exists(cached_file_path) and
147 cached_update_file = os.path.join(image_path, 'update.gz') 117 os.path.getmtime(cached_file_path) < os.path.getmtime(image_path)):
118 return True
148 else: 119 else:
149 cached_update_file = os.path.join(self.static_dir, 'update.gz') 120 web.debug('Found usable cached update image at %s instead of %s' %
150 121 (cached_file_path, image_path))
151 # If the new chromiumos image is newer, re-create everything. 122 return False
152 if (os.path.exists(cached_update_file) and 123
153 os.path.getmtime(cached_update_file) >= os.path.getmtime(bin_path)): 124 def _GetSize(self, update_path):
154 web.debug('Using cached update image at %s instead of %s' % 125 """Returns the size of the file given."""
155 (cached_update_file, bin_path)) 126 return os.path.getsize(update_path)
127
128 def _GetHash(self, update_path):
129 """Returns the sha1 of the file given."""
130 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
131 % update_path)
132 return os.popen(cmd).read().rstrip()
133
134 def GetUpdatePayload(self, hash, size, url):
135 """Returns a payload to the client corresponding to a new update.
136
137 Args:
138 hash: hash of update blob
139 size: size of update blob
140 url: where to find update blob
141 Returns:
142 Xml string to be passed back to client.
143 """
144 payload = """<?xml version="1.0" encoding="UTF-8"?>
145 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
146 <daystart elapsed_seconds="%s"/>
147 <app appid="{%s}" status="ok">
148 <ping status="ok"/>
149 <updatecheck
150 codebase="%s"
151 hash="%s"
152 needsadmin="false"
153 size="%s"
154 status="ok"/>
155 </app>
156 </gupdate>
157 """
158 return payload % (self._GetSecondsSinceMidnight(),
159 self.app_id, url, hash, size)
160
161 def GetNoUpdatePayload(self):
162 """Returns a payload to the client corresponding to no update."""
163 payload = """ < ?xml version = "1.0" encoding = "UTF-8"? >
164 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0 " >
165 < daystart elapsed_seconds = "%s" />
166 < app appid = "{%s}" status = "ok" >
167 < ping status = "ok" />
168 < updatecheck status = "noupdate" />
169 </ app >
170 </ gupdate >
171 """
172 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
173
174 def GenerateUpdateFile(self, image_path):
175 """Generates an update gz given a full path to an image.
176
177 Args:
178 image_path: Full path to image.
179 Returns:
180 Path to created update_payload or None on error.
181 """
182 image_dir = os.path.dirname(image_path)
183 update_path = os.path.join(image_dir, 'update.gz')
184 web.debug('Generating update image %s' % update_path)
185
186 mkupdate_command = (
187 '%s/cros_generate_update_payload --image=%s --output=%s '
188 '--patch_kernel' % (self.scripts_dir, image_path, update_path))
189 if os.system(mkupdate_command) != 0:
190 web.debug('Failed to create base update file')
191 return None
192
193 return update_path
194
195 def GenerateStatefulFile(self, image_path):
196 """Generates a stateful update gz given a full path to an image.
197
198 Args:
199 image_path: Full path to image.
200 Returns:
201 Path to created stateful update_payload or None on error.
202 """
203 stateful_partition_path = '%s/stateful.image' % os.path.dirname(image_path)
204
205 # Unpack to get stateful partition.
206 if self._UnpackStatefulPartition(image_path, stateful_partition_path):
207 mkstatefulupdate_command = 'gzip -f %s' % stateful_partition_path
208 if os.system(mkstatefulupdate_command) == 0:
209 web.debug('Successfully generated %s.gz' % stateful_partition_path)
210 return '%s.gz' % stateful_partition_path
211
212 web.debug('Failed to create stateful update file')
213 return None
214
215 def MoveImagesToStaticDir(self, update_path, stateful_update_path,
216 static_image_dir):
217 """Moves gz files from their directories to serving directories.
218
219 Args:
220 update_path: full path to main update gz.
221 stateful_update_path: full path to stateful partition gz.
222 static_image_dir: where to put files.
223 Returns:
224 Returns True if the files were moved over successfully.
225 """
226 try:
227 shutil.copy(update_path, static_image_dir)
228 shutil.copy(stateful_update_path, static_image_dir)
229 os.remove(update_path)
230 os.remove(stateful_update_path)
231 except Exception:
232 web.debug('Failed to move %s and %s to %s' % (update_path,
233 stateful_update_path,
234 static_image_dir))
235 return False
236
237 return True
238
239 def GenerateUpdateImage(self, image_path, move_to_static_dir=False,
240 static_image_dir=None):
241 """Force generates an update payload based on the given image_path.
242
243 Args:
244 image_path: full path to the image.
245 move_to_static_dir: Moves the files from their dir to the static dir.
246 static_image_dir: the directory to move images to after generating.
247 Returns:
248 True if the update payload was created successfully.
249 """
250 web.debug('Generating update for image %s' % image_path)
251 update_path = self.GenerateUpdateFile(image_path)
252 stateful_update_path = self.GenerateStatefulFile(image_path)
253 if not update_path or not stateful_update_path:
254 web.debug('Failed to generate update')
255 return False
256
257 if move_to_static_dir:
258 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
259 static_image_dir)
156 else: 260 else:
157 # Unpack zip file if we are serving from a directory. 261 return True
158 if self.serve_only and not self.UnpackZip(image_path, image_file): 262
159 web.debug('unzip image.zip failed.') 263 def GenerateLatestUpdateImage(self, board_id, client_version,
160 return False 264 static_image_dir=None):
161 265 """Generates an update using the latest image that has been built.
162 update_file = os.path.join(image_path, 'update.gz') 266
163 web.debug('Generating update image %s' % update_file) 267 This will only generate an update if the newest update is newer than that
164 mkupdate_command = ( 268 on the client or client_version is 'ForcedUpdate'.
165 '%s/cros_generate_update_payload --image=%s --output=%s ' 269
166 '--patch_kernel' % (self.scripts_dir, bin_path, update_file)) 270 Args:
167 if os.system(mkupdate_command) != 0: 271 board_id: Name of the board.
168 web.debug('Failed to create update image') 272 client_version: Current version of the client or 'ForcedUpdate'
169 return False 273 static_image_dir: the directory to move images to after generating.
170 274 Returns:
171 # Unpack to get stateful partition. 275 True if the update payload was created successfully.
172 if not self.UnpackStatefulPartition(image_path, image_file, 276 """
173 stateful_file): 277 latest_image_dir = self._GetLatestImageDir(board_id)
174 web.debug('Failed to unpack stateful partition.') 278 latest_version = self._GetVersionFromDir(latest_image_dir)
175 return False 279 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
176 280
177 mkstatefulupdate_command = 'gzip -f %s' % stateful_file 281 web.debug('Preparing to generate update from latest built image %s.' %
178 if os.system(mkstatefulupdate_command) != 0: 282 latest_image_path)
179 web.debug('Failed to create stateful update gz') 283
180 return False 284 # Check to see whether or not we should update.
181 285 if client_version != 'ForcedUpdate' and not self._CanUpdate(
182 # Add gz suffix 286 client_version, latest_version):
183 stateful_file = '%s.gz' % stateful_file 287 web.debug('no update')
184 288 return False
185 # Cleanup of image files. 289
186 if not self.serve_only: 290 cached_file_path = os.path.join(static_image_dir, 'update.gz')
187 try: 291 if (os.path.exists(cached_file_path) and
188 web.debug('Found a new image to serve, copying it to static') 292 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
189 shutil.copy(update_file, self.static_dir) 293 return True
190 shutil.copy(stateful_file, self.static_dir) 294
191 os.remove(update_file) 295 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
192 os.remove(stateful_file) 296 static_image_dir=static_image_dir)
193 except Exception, e: 297
194 web.debug('%s' % e) 298 def GenerateImageFromZip(self, static_image_dir):
195 return False 299 """Generates an update from an image zip file.
196 return True 300
197 301 This method assumes you have an image.zip in directory you are serving
198 def GetSize(self, update_path): 302 from. If this file is newer than a previously cached file, it will unzip
199 return os.path.getsize(update_path) 303 this file, create a payload and serve it.
200 304
201 def GetHash(self, update_path): 305 Args:
202 cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \ 306 static_image_dir: Directory where the zip file exists.
203 % update_path 307 Returns:
204 return os.popen(cmd).read().rstrip() 308 True if the update payload was created successfully.
309 """
310 web.debug('Preparing to generate update from zip in %s.' % static_image_dir)
311 image_path = os.path.join(static_image_dir, self._GetImageName())
312 cached_file_path = os.path.join(static_image_dir, 'update.gz')
313 if not self._IsImageNewerThanCached(image_path, cached_file_path):
314 return True
315
316 if self._UnpackZip(static_image_dir):
317 web.debug('unzip image.zip failed.')
318 return False
319
320 return self.GenerateUpdateImage(image_path, move_to_static_dir=False,
321 static_image_dir=None)
205 322
206 def ImportFactoryConfigFile(self, filename, validate_checksums=False): 323 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
207 """Imports a factory-floor server configuration file. The file should 324 """Imports a factory-floor server configuration file. The file should
208 be in this format: 325 be in this format:
209 config = [ 326 config = [
210 { 327 {
211 'qual_ids': set([1, 2, 3, "x86-generic"]), 328 'qual_ids': set([1, 2, 3, "x86-generic"]),
212 'factory_image': 'generic-factory.gz', 329 'factory_image': 'generic-factory.gz',
213 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', 330 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
214 'release_image': 'generic-release.gz', 331 'release_image': 'generic-release.gz',
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
247 f = open(filename, 'r') 364 f = open(filename, 'r')
248 output = {} 365 output = {}
249 exec(f.read(), output) 366 exec(f.read(), output)
250 self.factory_config = output['config'] 367 self.factory_config = output['config']
251 success = True 368 success = True
252 for stanza in self.factory_config: 369 for stanza in self.factory_config:
253 for key in stanza.copy().iterkeys(): 370 for key in stanza.copy().iterkeys():
254 suffix = '_image' 371 suffix = '_image'
255 if key.endswith(suffix): 372 if key.endswith(suffix):
256 kind = key[:-len(suffix)] 373 kind = key[:-len(suffix)]
257 stanza[kind + '_size'] = \ 374 stanza[kind + '_size'] = os.path.getsize(os.path.join(
258 os.path.getsize(self.static_dir + '/' + stanza[kind + '_image']) 375 self.static_dir, stanza[kind + '_image']))
259 if validate_checksums: 376 if validate_checksums:
260 factory_checksum = self.GetHash(self.static_dir + '/' + 377 factory_checksum = self._GetHash(self.static_dir + ' / ' +
261 stanza[kind + '_image']) 378 stanza[kind + '_image'])
262 if factory_checksum != stanza[kind + '_checksum']: 379 if factory_checksum != stanza[kind + '_checksum']:
263 print 'Error: checksum mismatch for %s. Expected "%s" but file ' \ 380 print ('Error: checksum mismatch for %s. Expected "%s" but file '
264 'has checksum "%s".' % (stanza[kind + '_image'], 381 'has checksum "%s".' % (stanza[kind + '_image'],
265 stanza[kind + '_checksum'], 382 stanza[kind + '_checksum'],
266 factory_checksum) 383 factory_checksum))
267 success = False 384 success = False
385
268 if validate_checksums: 386 if validate_checksums:
269 if success is False: 387 if success is False:
270 raise Exception('Checksum mismatch in conf file.') 388 raise Exception('Checksum mismatch in conf file.')
389
271 print 'Config file looks good.' 390 print 'Config file looks good.'
272 391
273 def GetFactoryImage(self, board_id, channel): 392 def GetFactoryImage(self, board_id, channel):
274 kind = channel.rsplit('-', 1)[0] 393 kind = channel.rsplit(' - ', 1)[0]
275 for stanza in self.factory_config: 394 for stanza in self.factory_config:
276 if board_id not in stanza['qual_ids']: 395 if board_id not in stanza['qual_ids']:
277 continue 396 continue
278 if kind + '_image' not in stanza: 397 if kind + '_image' not in stanza:
279 break 398 break
280 return (stanza[kind + '_image'], 399 return (stanza[kind + '_image'],
281 stanza[kind + '_checksum'], 400 stanza[kind + '_checksum'],
282 stanza[kind + '_size']) 401 stanza[kind + '_size'])
283 return (None, None, None) 402 return (None, None, None)
284 403
404 def HandleFactoryRequest(self, hostname, board_id, channel):
405 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
406 if filename is None:
407 web.debug('unable to find image for board %s' % board_id)
408 return self.GetNoUpdatePayload()
409 url = 'http://%s/static/%s' % (hostname, filename)
410 web.debug('returning update payload ' + url)
411 return self.GetUpdatePayload(checksum, size, url)
412
285 def HandleUpdatePing(self, data, label=None): 413 def HandleUpdatePing(self, data, label=None):
286 web.debug('handle update ping: %s' % data) 414 """Handles an update ping from an update client.
415
416 Args:
417 data: xml blob from client.
418 label: optional label for the update.
419 Returns:
420 Update payload message for client.
421 """
422 web.debug('handling update ping: %s' % data)
287 update_dom = minidom.parseString(data) 423 update_dom = minidom.parseString(data)
288 root = update_dom.firstChild 424 root = update_dom.firstChild
289 if root.hasAttribute('updaterversion') and \ 425
290 not root.getAttribute('updaterversion').startswith( 426 # Check the client prefix to make sure you can support this type of update.
291 self.client_prefix): 427 if (root.hasAttribute('updaterversion') and
292 web.debug('Got update from unsupported updater:' + \ 428 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
293 root.getAttribute('updaterversion')) 429 web.debug('Got update from unsupported updater:' +
430 root.getAttribute('updaterversion'))
294 return self.GetNoUpdatePayload() 431 return self.GetNoUpdatePayload()
432
433 # We only generate update payloads for updatecheck requests.
434 update_check = root.getElementsByTagName('o:updatecheck')
435 if not update_check:
436 web.debug('Non-update check received. Returning blank payload.')
437 # TODO(sosa): Generate correct non-updatecheck payload to better test
438 # update clients.
439 return self.GetNoUpdatePayload()
440
441 # Since this is an updatecheck, get information about the requester.
442 hostname = web.ctx.host
295 query = root.getElementsByTagName('o:app')[0] 443 query = root.getElementsByTagName('o:app')[0]
296 client_version = query.getAttribute('version') 444 client_version = query.getAttribute('version')
297 channel = query.getAttribute('track') 445 channel = query.getAttribute('track')
298 board_id = query.hasAttribute('board') and query.getAttribute('board') \ 446 board_id = (query.hasAttribute('board') and query.getAttribute('board')
299 or self.GetDefaultBoardID() 447 or self._GetDefaultBoardID())
300 latest_image_path = self.GetLatestImagePath(board_id)
301 latest_version = self.GetLatestVersion(latest_image_path)
302 hostname = web.ctx.host
303 448
304 # If this is a factory floor server, return the image here: 449 # Separate logic as Factory requests have static url's that override
450 # other options.
305 if self.factory_config: 451 if self.factory_config:
306 (filename, checksum, size) = \ 452 return self.HandleFactoryRequest(hostname, board_id, channel)
307 self.GetFactoryImage(board_id, channel) 453 else:
308 if filename is None: 454 static_image_dir = self.static_dir
309 web.debug('unable to find image for board %s' % board_id) 455 if label:
456 static_image_dir = os.path.join(static_image_dir, label)
457
458 # Not for factory, find and serve the correct image given the options.
459 if self.forced_image:
460 has_built_image = self.GenerateUpdateImage(
461 self.forced_image, move_to_static_dir=True,
462 static_image_dir=static_image_dir)
463 # Now that we've generated it, clear out so that other pings of same
464 # devserver instance do not generate new images.
465 self.forced_image = None
466 elif self.serve_only:
467 has_built_image = self.GenerateImageFromZip(static_image_dir)
468 else:
469 has_built_image = self.GenerateLatestUpdateImage(board_id,
470 client_version,
471 static_image_dir)
472
473 if has_built_image:
474 hash = self._GetHash(os.path.join(static_image_dir, 'update.gz'))
475 size = self._GetSize(os.path.join(static_image_dir, 'update.gz'))
476 if self.static_urlbase and label:
477 url = '%s/%s/update.gz' % (self.static_urlbase, label)
478 elif self.serve_only:
479 url = 'http://%s/static/archive/update.gz' % hostname
480 else:
481 url = 'http://%s/static/update.gz' % hostname
482 return self.GetUpdatePayload(hash, size, url)
483 else:
310 return self.GetNoUpdatePayload() 484 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')
345 else:
346 web.debug('update found %s ' % latest_version)
347 ok = self.BuildUpdateImage(latest_image_path)
348 if ok != True:
349 web.debug('Failed to build an update image')
350 return self.GetNoUpdatePayload()
351
352 hash = self.GetHash('%s/update.gz' % self.static_dir)
353 size = self.GetSize('%s/update.gz' % self.static_dir)
354
355 url = 'http://%s/static/update.gz' % hostname
356 return self.GetUpdatePayload(hash, size, url)
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