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

Side by Side Diff: autoupdate.py

Issue 3460003: Revert "TEST=run minimomaha server. Confirm identical tolast working revision." (Closed) Base URL: http://git.chromium.org/git/dev-util.git
Patch Set: Fix auto correct 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' % image_dir) == 0
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'
136 else: 105 else:
137 image_file = 'chromiumos_image.bin' 106 image_name = 'chromiumos_image.bin'
138 return image_file 107 return image_name
139 108
140 def BuildUpdateImage(self, image_path): 109 def _IsImageNewerThanCached(self, image_path, cached_file_path):
141 stateful_file = '%s/stateful.image' % image_path 110 """Returns true if the image is newer than the cached image."""
142 image_file = self.GetImageBinPath(image_path) 111 if os.path.exists(cached_file_path) and os.path.exists(image_path):
143 bin_path = os.path.join(image_path, image_file) 112 web.debug('Usable cached image found.')
144 113 return os.path.getmtime(image_path) > os.path.getmtime(cached_file_path)
145 # Get appropriate update.gz to compare timestamps. 114 elif not os.path.exists(cached_file_path) and not os.path.exists(image_path) :
146 if self.serve_only: 115 raise Exception('Image does not exist and cached image missing')
147 cached_update_file = os.path.join(image_path, 'update.gz')
148 else: 116 else:
149 cached_update_file = os.path.join(self.static_dir, 'update.gz') 117 # Only one is missing, figure out which one.
150 118 if os.path.exists(image_path):
151 # If the new chromiumos image is newer, re-create everything. 119 web.debug('No cached image found - image generation required.')
152 if (os.path.exists(cached_update_file) and 120 return True
153 os.path.getmtime(cached_update_file) >= os.path.getmtime(bin_path)): 121 else:
154 web.debug('Using cached update image at %s instead of %s' % 122 web.debug('Only cached image found to serve.')
155 (cached_update_file, bin_path)) 123 return False
124
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
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)
156 else: 261 else:
157 # Unpack zip file if we are serving from a directory. 262 return True
158 if self.serve_only and not self.UnpackZip(image_path, image_file): 263
159 web.debug('unzip image.zip failed.') 264 def GenerateLatestUpdateImage(self, board_id, client_version,
160 return False 265 static_image_dir=None):
161 266 """Generates an update using the latest image that has been built.
162 update_file = os.path.join(image_path, 'update.gz') 267
163 web.debug('Generating update image %s' % update_file) 268 This will only generate an update if the newest update is newer than that
164 mkupdate_command = ( 269 on the client or client_version is 'ForcedUpdate'.
165 '%s/cros_generate_update_payload --image=%s --output=%s ' 270
166 '--patch_kernel' % (self.scripts_dir, bin_path, update_file)) 271 Args:
167 if os.system(mkupdate_command) != 0: 272 board_id: Name of the board.
168 web.debug('Failed to create update image') 273 client_version: Current version of the client or 'ForcedUpdate'
169 return False 274 static_image_dir: the directory to move images to after generating.
170 275 Returns:
171 # Unpack to get stateful partition. 276 True if the update payload was created successfully.
172 if not self.UnpackStatefulPartition(image_path, image_file, 277 """
173 stateful_file): 278 latest_image_dir = self._GetLatestImageDir(board_id)
174 web.debug('Failed to unpack stateful partition.') 279 latest_version = self._GetVersionFromDir(latest_image_dir)
175 return False 280 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
176 281
177 mkstatefulupdate_command = 'gzip -f %s' % stateful_file 282 web.debug('Preparing to generate update from latest built image %s.' %
178 if os.system(mkstatefulupdate_command) != 0: 283 latest_image_path)
179 web.debug('Failed to create stateful update gz') 284
180 return False 285 # Check to see whether or not we should update.
181 286 if client_version != 'ForcedUpdate' and not self._CanUpdate(
182 # Add gz suffix 287 client_version, latest_version):
183 stateful_file = '%s.gz' % stateful_file 288 web.debug('no update')
184 289 return False
185 # Cleanup of image files. 290
186 if not self.serve_only: 291 cached_file_path = os.path.join(static_image_dir, 'update.gz')
187 try: 292 if (os.path.exists(cached_file_path) and
188 web.debug('Found a new image to serve, copying it to static') 293 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
189 shutil.copy(update_file, self.static_dir) 294 return True
190 shutil.copy(stateful_file, self.static_dir) 295
191 os.remove(update_file) 296 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
192 os.remove(stateful_file) 297 static_image_dir=static_image_dir)
193 except Exception, e: 298
194 web.debug('%s' % e) 299 def GenerateImageFromZip(self, static_image_dir):
195 return False 300 """Generates an update from an image zip file.
196 return True 301
197 302 This method assumes you have an image.zip in directory you are serving
198 def GetSize(self, update_path): 303 from. If this file is newer than a previously cached file, it will unzip
199 return os.path.getsize(update_path) 304 this file, create a payload and serve it.
200 305
201 def GetHash(self, update_path): 306 Args:
202 cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \ 307 static_image_dir: Directory where the zip file exists.
203 % update_path 308 Returns:
204 return os.popen(cmd).read().rstrip() 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)
205 324
206 def ImportFactoryConfigFile(self, filename, validate_checksums=False): 325 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
207 """Imports a factory-floor server configuration file. The file should 326 """Imports a factory-floor server configuration file. The file should
208 be in this format: 327 be in this format:
209 config = [ 328 config = [
210 { 329 {
211 'qual_ids': set([1, 2, 3, "x86-generic"]), 330 'qual_ids': set([1, 2, 3, "x86-generic"]),
212 'factory_image': 'generic-factory.gz', 331 'factory_image': 'generic-factory.gz',
213 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', 332 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
214 'release_image': 'generic-release.gz', 333 'release_image': 'generic-release.gz',
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
247 f = open(filename, 'r') 366 f = open(filename, 'r')
248 output = {} 367 output = {}
249 exec(f.read(), output) 368 exec(f.read(), output)
250 self.factory_config = output['config'] 369 self.factory_config = output['config']
251 success = True 370 success = True
252 for stanza in self.factory_config: 371 for stanza in self.factory_config:
253 for key in stanza.copy().iterkeys(): 372 for key in stanza.copy().iterkeys():
254 suffix = '_image' 373 suffix = '_image'
255 if key.endswith(suffix): 374 if key.endswith(suffix):
256 kind = key[:-len(suffix)] 375 kind = key[:-len(suffix)]
257 stanza[kind + '_size'] = \ 376 stanza[kind + '_size'] = self._GetSize(os.path.join(
258 os.path.getsize(self.static_dir + '/' + stanza[kind + '_image']) 377 self.static_dir, stanza[kind + '_image']))
259 if validate_checksums: 378 if validate_checksums:
260 factory_checksum = self.GetHash(self.static_dir + '/' + 379 factory_checksum = self._GetHash(os.path.join(self.static_dir,
261 stanza[kind + '_image']) 380 stanza[kind + '_image']))
262 if factory_checksum != stanza[kind + '_checksum']: 381 if factory_checksum != stanza[kind + '_checksum']:
263 print 'Error: checksum mismatch for %s. Expected "%s" but file ' \ 382 print ('Error: checksum mismatch for %s. Expected "%s" but file '
264 'has checksum "%s".' % (stanza[kind + '_image'], 383 'has checksum "%s".' % (stanza[kind + '_image'],
265 stanza[kind + '_checksum'], 384 stanza[kind + '_checksum'],
266 factory_checksum) 385 factory_checksum))
267 success = False 386 success = False
387
268 if validate_checksums: 388 if validate_checksums:
269 if success is False: 389 if success is False:
270 raise Exception('Checksum mismatch in conf file.') 390 raise Exception('Checksum mismatch in conf file.')
391
271 print 'Config file looks good.' 392 print 'Config file looks good.'
272 393
273 def GetFactoryImage(self, board_id, channel): 394 def GetFactoryImage(self, board_id, channel):
274 kind = channel.rsplit('-', 1)[0] 395 kind = channel.rsplit('-', 1)[0]
275 for stanza in self.factory_config: 396 for stanza in self.factory_config:
276 if board_id not in stanza['qual_ids']: 397 if board_id not in stanza['qual_ids']:
277 continue 398 continue
278 if kind + '_image' not in stanza: 399 if kind + '_image' not in stanza:
279 break 400 break
280 return (stanza[kind + '_image'], 401 return (stanza[kind + '_image'],
281 stanza[kind + '_checksum'], 402 stanza[kind + '_checksum'],
282 stanza[kind + '_size']) 403 stanza[kind + '_size'])
283 return (None, None, None) 404 return (None, None, None)
284 405
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
285 def HandleUpdatePing(self, data, label=None): 415 def HandleUpdatePing(self, data, label=None):
286 web.debug('handle update ping: %s' % data) 416 """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)
287 update_dom = minidom.parseString(data) 425 update_dom = minidom.parseString(data)
288 root = update_dom.firstChild 426 root = update_dom.firstChild
289 if root.hasAttribute('updaterversion') and \ 427
290 not root.getAttribute('updaterversion').startswith( 428 # Check the client prefix to make sure you can support this type of update.
291 self.client_prefix): 429 if (root.hasAttribute('updaterversion') and
292 web.debug('Got update from unsupported updater:' + \ 430 not root.getAttribute('updaterversion').startswith(self.client_prefix)):
293 root.getAttribute('updaterversion')) 431 web.debug('Got update from unsupported updater:' +
432 root.getAttribute('updaterversion'))
294 return self.GetNoUpdatePayload() 433 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
295 query = root.getElementsByTagName('o:app')[0] 445 query = root.getElementsByTagName('o:app')[0]
296 client_version = query.getAttribute('version') 446 client_version = query.getAttribute('version')
297 channel = query.getAttribute('track') 447 channel = query.getAttribute('track')
298 board_id = query.hasAttribute('board') and query.getAttribute('board') \ 448 board_id = (query.hasAttribute('board') and query.getAttribute('board')
299 or self.GetDefaultBoardID() 449 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 450
304 # If this is a factory floor server, return the image here: 451 # Separate logic as Factory requests have static url's that override
452 # other options.
305 if self.factory_config: 453 if self.factory_config:
306 (filename, checksum, size) = \ 454 return self.HandleFactoryRequest(hostname, board_id, channel)
307 self.GetFactoryImage(board_id, channel) 455 else:
308 if filename is None: 456 static_image_dir = self.static_dir
309 web.debug('unable to find image for board %s' % board_id) 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:
310 return self.GetNoUpdatePayload() 486 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