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

Side by Side Diff: autoupdate.py

Issue 4906001: Reworked devserver so that update images generated are cached in directories named after (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/dev-util.git@master
Patch Set: Cached payloads are now symlinked from old/known paths. Created 10 years, 1 month 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-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 import cherrypy 8 import cherrypy
8 import os 9 import os
9 import shutil 10 import shutil
10 import subprocess 11 import subprocess
11 import tempfile
12 import time 12 import time
13 13
14 14
15 def _LogMessage(message): 15 def _LogMessage(message):
16 cherrypy.log(message, 'UPDATE') 16 cherrypy.log(message, 'UPDATE')
17 17
18 18
19 class Autoupdate(BuildObject): 19 class Autoupdate(BuildObject):
20 """Class that contains functionality that handles Chrome OS update pings. 20 """Class that contains functionality that handles Chrome OS update pings.
21 21
22 Members: 22 Members:
23 serve_only: Serve images from a pre-built image.zip file. static_dir 23 serve_only: Serve images from a pre-built image.zip file. static_dir
24 must be set to the location of the image.zip. 24 must be set to the location of the image.zip.
25 factory_config: Path to the factory config file if handling factory 25 factory_config: Path to the factory config file if handling factory
26 requests. 26 requests.
27 use_test_image: Use chromiumos_test_image.bin rather than the standard. 27 use_test_image: Use chromiumos_test_image.bin rather than the standard.
28 static_url_base: base URL, other than devserver, for update images. 28 static_url_base: base URL, other than devserver, for update images.
29 client_prefix: The prefix for the update engine client. 29 client_prefix: The prefix for the update engine client.
30 forced_image: Path to an image to use for all updates. 30 forced_image: Path to an image to use for all updates.
31 """ 31 """
32 32
33 def __init__(self, serve_only=None, test_image=False, urlbase=None, 33 def __init__(self, serve_only=None, test_image=False, urlbase=None,
34 factory_config_path=None, client_prefix=None, forced_image=None, 34 factory_config_path=None, client_prefix=None, forced_image=None,
35 use_cached=False, port=8080, src_image='', vm=False, board=None, 35 port=8080, src_image='', vm=False, board=None,
36 *args, **kwargs): 36 *args, **kwargs):
37 super(Autoupdate, self).__init__(*args, **kwargs) 37 super(Autoupdate, self).__init__(*args, **kwargs)
38 self.serve_only = serve_only 38 self.serve_only = serve_only
39 self.factory_config = factory_config_path 39 self.factory_config = factory_config_path
40 self.use_test_image = test_image 40 self.use_test_image = test_image
41 if urlbase: 41 if urlbase:
42 self.urlbase = urlbase 42 self.urlbase = urlbase
43 else: 43 else:
44 self.urlbase = None 44 self.urlbase = None
45 45
46 self.client_prefix = client_prefix 46 self.client_prefix = client_prefix
47 self.forced_image = forced_image 47 self.forced_image = forced_image
48 self.use_cached = use_cached
49 self.src_image = src_image 48 self.src_image = src_image
50 self.vm = vm 49 self.vm = vm
51 self.board = board 50 self.board = board
52 self.crosutils = os.path.join(os.path.dirname(__file__), '../../scripts') 51 self.crosutils = os.path.join(os.path.dirname(__file__), '../../scripts')
53 52
54 def _GetSecondsSinceMidnight(self): 53 def _GetSecondsSinceMidnight(self):
55 """Returns the seconds since midnight as a decimal value.""" 54 """Returns the seconds since midnight as a decimal value."""
56 now = time.localtime() 55 now = time.localtime()
57 return now[3] * 3600 + now[4] * 60 + now[5] 56 return now[3] * 3600 + now[4] * 60 + now[5]
58 57
59 def _GetDefaultBoardID(self): 58 def _GetDefaultBoardID(self):
60 """Returns the default board id stored in .default_board.""" 59 """Returns the default board id stored in .default_board."""
61 board_file = '%s/.default_board' % (self.scripts_dir) 60 board_file = '%s/.default_board' % (self.scripts_dir)
62 try: 61 try:
63 return open(board_file).read() 62 return open(board_file).read()
64 except IOError: 63 except IOError:
65 return 'x86-generic' 64 return 'x86-generic'
66 65
67 def _GetLatestImageDir(self, board_id): 66 def _GetLatestImageDir(self, board_id):
68 """Returns the latest image dir based on shell script.""" 67 """Returns the latest image dir based on shell script."""
69 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id) 68 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
70 return os.popen(cmd).read().strip() 69 return os.popen(cmd).read().strip()
71 70
72 def _GetVersionFromDir(self, image_dir): 71 def _GetVersionFromDir(self, image_dir):
73 """Returns the version of the image based on the name of the directory.""" 72 """Returns the version of the image based on the name of the directory."""
74 latest_version = os.path.basename(image_dir) 73 latest_version = os.path.basename(image_dir)
75 return latest_version.split('-')[0] 74 return latest_version.split('-')[0]
76 75
77 def _CanUpdate(self, client_version, latest_version): 76 def _CanUpdate(self, client_version, latest_version):
78 """Returns true if the latest_version is greater than the client_version.""" 77 """Returns true if the latest_version is greater than the client_version.
78 """
79 client_tokens = client_version.replace('_', '').split('.') 79 client_tokens = client_version.replace('_', '').split('.')
80 latest_tokens = latest_version.replace('_', '').split('.') 80 latest_tokens = latest_version.replace('_', '').split('.')
81 _LogMessage('client version %s latest version %s' 81 _LogMessage('client version %s latest version %s'
82 % (client_version, latest_version)) 82 % (client_version, latest_version))
83 for i in range(4): 83 for i in range(4):
84 if int(latest_tokens[i]) == int(client_tokens[i]): 84 if int(latest_tokens[i]) == int(client_tokens[i]):
85 continue 85 continue
86 return int(latest_tokens[i]) > int(client_tokens[i]) 86 return int(latest_tokens[i]) > int(client_tokens[i])
87 return False 87 return False
88 88
89 def _UnpackZip(self, image_dir): 89 def _UnpackZip(self, image_dir):
90 """Unpacks an image.zip into a given directory.""" 90 """Unpacks an image.zip into a given directory."""
91 image = os.path.join(image_dir, self._GetImageName()) 91 image = os.path.join(image_dir, self._GetImageName())
92 if os.path.exists(image): 92 if os.path.exists(image):
93 return True 93 return True
94 else: 94 else:
95 # -n, never clobber an existing file, in case we get invoked 95 # -n, never clobber an existing file, in case we get invoked
96 # simultaneously by multiple request handlers. This means that 96 # simultaneously by multiple request handlers. This means that
97 # we're assuming each image.zip file lives in a versioned 97 # we're assuming each image.zip file lives in a versioned
98 # directory (a la Buildbot). 98 # directory (a la Buildbot).
99 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0 99 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
100 100
101 def _GetImageName(self): 101 def _GetImageName(self):
102 """Returns the name of the image that should be used.""" 102 """Returns the name of the image that should be used."""
103 if self.use_test_image: 103 if self.use_test_image:
104 image_name = 'chromiumos_test_image.bin' 104 image_name = 'chromiumos_test_image.bin'
105 else: 105 else:
106 image_name = 'chromiumos_image.bin' 106 image_name = 'chromiumos_image.bin'
107 return image_name 107 return image_name
108 108
109 def _IsImageNewerThanCached(self, image_path, cached_file_path):
110 """Returns true if the image is newer than the cached image."""
111 if os.path.exists(cached_file_path) and os.path.exists(image_path):
112 _LogMessage('Usable cached image found at %s.' % cached_file_path)
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')
116 else:
117 # Only one is missing, figure out which one.
118 if os.path.exists(image_path):
119 _LogMessage('No cached image found - image generation required.')
120 return True
121 else:
122 _LogMessage('Cached image found to serve at %s.' % cached_file_path)
123 return False
124
125 def _GetSize(self, update_path): 109 def _GetSize(self, update_path):
126 """Returns the size of the file given.""" 110 """Returns the size of the file given."""
127 return os.path.getsize(update_path) 111 return os.path.getsize(update_path)
128 112
129 def _GetHash(self, update_path): 113 def _GetHash(self, update_path):
130 """Returns the sha1 of the file given.""" 114 """Returns the sha1 of the file given."""
131 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';' 115 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
132 % update_path) 116 % update_path)
133 return os.popen(cmd).read().rstrip() 117 return os.popen(cmd).read().rstrip()
134 118
(...skipping 10 matching lines...) Expand all
145 # it takes advantage of reduced I/O and multiple processors. Something like: 129 # it takes advantage of reduced I/O and multiple processors. Something like:
146 # % tee < FILE > /dev/null \ 130 # % tee < FILE > /dev/null \
147 # >( openssl dgst -sha256 -binary | openssl base64 ) \ 131 # >( openssl dgst -sha256 -binary | openssl base64 ) \
148 # >( openssl sha1 -binary | openssl base64 ) 132 # >( openssl sha1 -binary | openssl base64 )
149 def _GetSHA256(self, update_path): 133 def _GetSHA256(self, update_path):
150 """Returns the sha256 of the file given.""" 134 """Returns the sha256 of the file given."""
151 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' % 135 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
152 update_path) 136 update_path)
153 return os.popen(cmd).read().rstrip() 137 return os.popen(cmd).read().rstrip()
154 138
139 def _GetMd5(self, update_path):
140 """Returns the md5 checksum of the file given."""
141 cmd = ("md5sum %s | awk '{print $1}'" % update_path)
142 return os.popen(cmd).read().rstrip()
143
144 def _Symlink(self, source, dest):
145 """Creates a symlink at dest to source"""
146 if os.path.exists(dest):
147 os.remove(dest)
sosa 2010/11/17 00:07:40 unlink better?
dgarrett 2010/11/17 00:35:27 Either way, they are synonyms.
148 os.symlink(source, dest)
149
155 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format): 150 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
156 """Returns a payload to the client corresponding to a new update. 151 """Returns a payload to the client corresponding to a new update.
157 152
158 Args: 153 Args:
159 hash: hash of update blob 154 hash: hash of update blob
160 sha256: SHA-256 hash of update blob 155 sha256: SHA-256 hash of update blob
161 size: size of update blob 156 size: size of update blob
162 url: where to find update blob 157 url: where to find update blob
163 Returns: 158 Returns:
164 Xml string to be passed back to client. 159 Xml string to be passed back to client.
(...skipping 26 matching lines...) Expand all
191 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0 " > 186 < gupdate xmlns = "http://www.google.com/update2/response" protocol = "2.0 " >
192 < daystart elapsed_seconds = "%s" /> 187 < daystart elapsed_seconds = "%s" />
193 < app appid = "{%s}" status = "ok" > 188 < app appid = "{%s}" status = "ok" >
194 < ping status = "ok" /> 189 < ping status = "ok" />
195 < updatecheck status = "noupdate" /> 190 < updatecheck status = "noupdate" />
196 </ app > 191 </ app >
197 </ gupdate > 192 </ gupdate >
198 """ 193 """
199 return payload % (self._GetSecondsSinceMidnight(), self.app_id) 194 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
200 195
201 def GenerateUpdateFile(self, image_path): 196 def GenerateUpdateFile(self, src_image, image_path, output_dir):
202 """Generates an update gz given a full path to an image. 197 """Generates an update gz given a full path to an image.
203 198
204 Args: 199 Args:
205 image_path: Full path to image. 200 image_path: Full path to image.
206 Returns: 201 Returns:
207 Path to created update_payload or None on error. 202 Path to created update_payload or None on error.
208 """ 203 """
209 image_dir = os.path.dirname(image_path) 204 update_path = os.path.join(output_dir, 'update.gz')
210 update_path = os.path.join(image_dir, 'update.gz')
211 patch_kernel_flag = '--patch_kernel' 205 patch_kernel_flag = '--patch_kernel'
212 _LogMessage('Generating update image %s' % update_path) 206 _LogMessage('Generating update image %s' % update_path)
213 207
214 # Don't patch the kernel for vm images as they don't need the patch. 208 # Don't patch the kernel for vm images as they don't need the patch.
215 if self.vm: 209 if self.vm:
216 patch_kernel_flag = '' 210 patch_kernel_flag = ''
217 211
218 mkupdate_command = ( 212 mkupdate_command = (
219 '%s/cros_generate_update_payload --image="%s" --output="%s" ' 213 '%s/cros_generate_update_payload --image="%s" --output="%s" '
220 '%s --noold_style --src_image="%s"' % ( 214 '%s --noold_style --src_image="%s"' % (
221 self.scripts_dir, image_path, update_path, patch_kernel_flag, 215 self.scripts_dir, image_path, update_path, patch_kernel_flag,
222 self.src_image)) 216 src_image))
223 _LogMessage(mkupdate_command) 217 _LogMessage(mkupdate_command)
224 if os.system(mkupdate_command) != 0: 218 if os.system(mkupdate_command) != 0:
225 _LogMessage('Failed to create base update file') 219 _LogMessage('Failed to create base update file')
226 return None 220 return None
227 221
228 return update_path 222 return update_path
229 223
230 def GenerateStatefulFile(self, image_path): 224 def GenerateStatefulFile(self, image_path, output_dir):
231 """Generates a stateful update given a full path to an image. 225 """Generates a stateful update payload given a full path to an image.
232 226
233 Args: 227 Args:
234 image_path: Full path to image. 228 image_path: Full path to image.
235 Returns: 229 Returns:
236 Path to created stateful update_payload. 230 Path to created stateful update_payload or None on error.
237 Raises: 231 Raises:
238 A subprocess exception if the update generator fails to generate a 232 A subprocess exception if the update generator fails to generate a
239 stateful payload. 233 stateful payload.
240 """ 234 """
241 work_dir = os.path.dirname(image_path) 235 output_gz = os.path.join(output_dir, 'stateful.tgz')
242 output_gz = os.path.join(work_dir, 'stateful.tgz')
243 subprocess.check_call( 236 subprocess.check_call(
244 ['%s/cros_generate_stateful_update_payload' % self.crosutils, 237 ['%s/cros_generate_stateful_update_payload' % self.crosutils,
245 '--image=%s' % image_path, 238 '--image=%s' % image_path,
246 '--output_dir=%s' % work_dir, 239 '--output_dir=%s' % output_dir,
247 ]) 240 ])
248 return output_gz 241 return output_gz
249 242
250 def MoveImagesToStaticDir(self, update_path, stateful_update_path, 243 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
251 static_image_dir): 244 """Find directory to store a cached update.
252 """Moves gz files from their directories to serving directories. 245
246 Given one, or two images for an update, this finds which
247 cache directory should hold the update files, even if they don't exist
248 yet. The directory will be inside static_image_dir, and of the form:
249
250 Non-delta updates:
251 cache/12345678
252
253 Delta updates:
254 cache/12345678_12345678
255 """
256 # If there is no src, we only have an image file, check image for changes
257 if not src_image:
258 return os.path.join('cache', self._GetMd5(dest_image))
259
260 # If we have src and dest, we are a delta, and check both for changes
261 return os.path.join('cache',
262 "%s_%s" % (self._GetMd5(src_image),
263 self._GetMd5(dest_image)))
264
265 def GenerateUpdateImage(self, src_image, image_path, output_dir):
266 """Force generates an update payload based on the given image_path.
253 267
254 Args: 268 Args:
255 update_path: full path to main update gz. 269 src_image: image we are updating from (Null/empty for non-delta)
256 stateful_update_path: full path to stateful partition gz. 270 image_path: full path to the image.
257 static_image_dir: where to put files. 271 output_dir: the directory to write the update payloads in
258 Returns: 272 Returns:
259 Returns True if the files were moved over successfully. 273 update and stateful payload with full file names
sosa 2010/11/17 00:07:40 tuple
dgarrett 2010/11/17 00:35:27 Done.
260 """ 274 """
261 try: 275 update_file = None
262 shutil.copy(update_path, static_image_dir) 276 stateful_update_file = None
263 shutil.copy(stateful_update_path, static_image_dir)
264 os.remove(update_path)
265 os.remove(stateful_update_path)
266 except Exception:
267 _LogMessage('Failed to move %s and %s to %s' % (update_path,
268 stateful_update_path,
269 static_image_dir))
270 return False
271 277
272 return True 278 # Actually do the generation
279 _LogMessage('Generating update for image %s' % image_path)
280 update_file = self.GenerateUpdateFile(src_image,
281 image_path,
282 output_dir)
273 283
274 def GenerateUpdateImage(self, image_path, move_to_static_dir=False, 284 if update_file:
275 static_image_dir=None): 285 stateful_update_file = self.GenerateStatefulFile(image_path,
276 """Generates an update payload based on the given image_path. 286 output_dir)
287
288 if update_file and stateful_update_file:
289 return update_file, stateful_update_file
290
291 _LogMessage('Failed to generate update')
292
293 # Cleanup incomplete files, if they exist
294 if update_file and os.path.exists(update_file):
295 os.remove(update_file)
296
297 return None
298
299 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
300 """Force generates an update payload based on the given image_path.
277 301
278 Args: 302 Args:
279 image_path: full path to the image. 303 image_path: full path to the image.
280 move_to_static_dir: Moves the files from their dir to the static dir.
281 static_image_dir: the directory to move images to after generating. 304 static_image_dir: the directory to move images to after generating.
282 Returns: 305 Returns:
283 True if the update payload was created successfully. 306 update filename (not directory) relative to static_image_dir on success,
307 or None
284 """ 308 """
285 _LogMessage('Generating update for image %s' % image_path) 309 _LogMessage('Generating update for src %s image %s' % (self.src_image,
286 update_path = self.GenerateUpdateFile(image_path) 310 image_path))
287 if update_path:
288 stateful_update_path = self.GenerateStatefulFile(image_path)
289 if move_to_static_dir:
290 return self.MoveImagesToStaticDir(update_path, stateful_update_path,
291 static_image_dir)
292 return True
293 311
294 _LogMessage('Failed to generate update') 312 # Which sub_dir of static_image_dir should hold our cached update image
295 return False 313 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
314 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir)
315
316 # The cached payloads exist in a cache dir
317 cache_update_payload = os.path.join(static_image_dir,
318 cache_sub_dir,
319 'update.gz')
320 cache_stateful_payload = os.path.join(static_image_dir,
321 cache_sub_dir,
322 'stateful.tgz')
323
324 # The final results (symlinks?) exist directly in static
325 update_payload = os.path.join(static_image_dir,
326 'update.gz')
327 stateful_payload = os.path.join(static_image_dir,
328 'stateful.tgz')
329
330 # If there isn't a cached payload, make one
331 if not os.path.exists(cache_update_payload):
sosa 2010/11/17 00:07:40 You have the possibility of this weird case where
dgarrett 2010/11/17 00:35:27 You might have created the cache directory, but no
332 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
333
334 # Create the directory for the cache values
335 if not os.path.exists(full_cache_dir):
336 os.makedirs(full_cache_dir)
337
338 payloads = self.GenerateUpdateImage(self.src_image,
339 image_path,
340 full_cache_dir)
341
342 if not payloads:
343 return None
344
345 # Verify they were created with the expected names
346 new_update_payload, new_stateful_payload = payloads
347
348 _LogMessage('"%s" "%s"' % (new_update_payload, cache_update_payload))
349 assert new_update_payload == cache_update_payload
350 _LogMessage('"%s" "%s"' % (new_stateful_payload, cache_stateful_payload))
351 assert new_stateful_payload == cache_stateful_payload
352
353 # If the generation worked, create symlinks
sosa 2010/11/17 00:39:41 Change comment ... more like ... Cache directory e
dgarrett 2010/11/17 00:56:02 No, you won't get here if the generation fails. Th
354 self._Symlink(cache_update_payload, update_payload)
355 self._Symlink(cache_stateful_payload, stateful_payload)
356
357 # return just the filename which is symlink in static_image_dir
sosa 2010/11/17 00:07:40 This might break archive_dir i.e. the test automat
dgarrett 2010/11/17 00:35:27 The names returned from here are relative to stati
358 return 'update.gz'
296 359
297 def GenerateLatestUpdateImage(self, board_id, client_version, 360 def GenerateLatestUpdateImage(self, board_id, client_version,
298 static_image_dir=None): 361 static_image_dir):
299 """Generates an update using the latest image that has been built. 362 """Generates an update using the latest image that has been built.
300 363
301 This will only generate an update if the newest update is newer than that 364 This will only generate an update if the newest update is newer than that
302 on the client or client_version is 'ForcedUpdate'. 365 on the client or client_version is 'ForcedUpdate'.
303 366
304 Args: 367 Args:
305 board_id: Name of the board. 368 board_id: Name of the board.
306 client_version: Current version of the client or 'ForcedUpdate' 369 client_version: Current version of the client or 'ForcedUpdate'
307 static_image_dir: the directory to move images to after generating. 370 static_image_dir: the directory to move images to after generating.
308 Returns: 371 Returns:
309 True if the update payload was created successfully. 372 Name of the update image relative to static_image_dir or None
310 """ 373 """
311 latest_image_dir = self._GetLatestImageDir(board_id) 374 latest_image_dir = self._GetLatestImageDir(board_id)
312 latest_version = self._GetVersionFromDir(latest_image_dir) 375 latest_version = self._GetVersionFromDir(latest_image_dir)
313 latest_image_path = os.path.join(latest_image_dir, self._GetImageName()) 376 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
314 377
315 _LogMessage('Preparing to generate update from latest built image %s.' % 378 _LogMessage('Preparing to generate update from latest built image %s.' %
316 latest_image_path) 379 latest_image_path)
317 380
318 # Check to see whether or not we should update. 381 # Check to see whether or not we should update.
319 if client_version != 'ForcedUpdate' and not self._CanUpdate( 382 if client_version != 'ForcedUpdate' and not self._CanUpdate(
320 client_version, latest_version): 383 client_version, latest_version):
321 _LogMessage('no update') 384 _LogMessage('no update')
322 return False 385 return None
323 386
324 cached_file_path = os.path.join(static_image_dir, 'update.gz') 387 return self.GenerateUpdateImageWithCache(latest_image_path,
325 if (os.path.exists(cached_file_path) and 388 static_image_dir=static_image_dir)
326 not self._IsImageNewerThanCached(latest_image_path, cached_file_path)):
327 return True
328
329 return self.GenerateUpdateImage(latest_image_path, move_to_static_dir=True,
330 static_image_dir=static_image_dir)
331 389
332 def GenerateImageFromZip(self, static_image_dir): 390 def GenerateImageFromZip(self, static_image_dir):
333 """Generates an update from an image zip file. 391 """Generates an update from an image zip file.
334 392
335 This method assumes you have an image.zip in directory you are serving 393 This method assumes you have an image.zip in directory you are serving
336 from. If this file is newer than a previously cached file, it will unzip 394 from. If this file is newer than a previously cached file, it will unzip
337 this file, create a payload and serve it. 395 this file, create a payload and serve it.
338 396
339 Args: 397 Args:
340 static_image_dir: Directory where the zip file exists. 398 static_image_dir: Directory where the zip file exists.
341 Returns: 399 Returns:
342 True if the update payload was created successfully. 400 Name of the update payload relative to static_image_dir if successful.
343 """ 401 """
344 _LogMessage('Preparing to generate update from zip in %s.' % static_image_di r) 402 _LogMessage('Preparing to generate update from zip in %s.' %
403 static_image_dir)
345 image_path = os.path.join(static_image_dir, self._GetImageName()) 404 image_path = os.path.join(static_image_dir, self._GetImageName())
346 cached_file_path = os.path.join(static_image_dir, 'update.gz')
347 zip_file_path = os.path.join(static_image_dir, 'image.zip') 405 zip_file_path = os.path.join(static_image_dir, 'image.zip')
348 if not self._IsImageNewerThanCached(zip_file_path, cached_file_path): 406
349 return True 407 # TODO(dgarrett): Either work caching into this path before
408 # we unpack, or remove zip support (sosa is considering).
409 # It does currently cache, but after the unpack.
350 410
351 if not self._UnpackZip(static_image_dir): 411 if not self._UnpackZip(static_image_dir):
352 _LogMessage('unzip image.zip failed.') 412 _LogMessage('unzip image.zip failed.')
353 return False 413 return None
354 414
355 return self.GenerateUpdateImage(image_path, move_to_static_dir=False, 415 return self.GenerateUpdateImageWithCache(image_path,
356 static_image_dir=None) 416 static_image_dir=static_image_dir)
357 417
358 def ImportFactoryConfigFile(self, filename, validate_checksums=False): 418 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
359 """Imports a factory-floor server configuration file. The file should 419 """Imports a factory-floor server configuration file. The file should
360 be in this format: 420 be in this format:
361 config = [ 421 config = [
362 { 422 {
363 'qual_ids': set([1, 2, 3, "x86-generic"]), 423 'qual_ids': set([1, 2, 3, "x86-generic"]),
364 'factory_image': 'generic-factory.gz', 424 'factory_image': 'generic-factory.gz',
365 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=', 425 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
366 'release_image': 'generic-release.gz', 426 'release_image': 'generic-release.gz',
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
443 return self.GetNoUpdatePayload() 503 return self.GetNoUpdatePayload()
444 url = '%s/static/%s' % (self.hostname, filename) 504 url = '%s/static/%s' % (self.hostname, filename)
445 is_delta_format = self._IsDeltaFormatFile(filename) 505 is_delta_format = self._IsDeltaFormatFile(filename)
446 _LogMessage('returning update payload ' + url) 506 _LogMessage('returning update payload ' + url)
447 # Factory install is using memento updater which is using the sha-1 hash so 507 # Factory install is using memento updater which is using the sha-1 hash so
448 # setting sha-256 to an empty string. 508 # setting sha-256 to an empty string.
449 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format) 509 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
450 510
451 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version, 511 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
452 static_image_dir): 512 static_image_dir):
453 """Generates an update for non-factory and returns True on success.""" 513 """Generates an update for non-factory image.
454 if self.use_cached and os.path.exists(os.path.join(static_image_dir, 514
455 'update.gz')): 515 Returns:
456 _LogMessage('Using cached image regardless of timestamps.') 516 file name relative to static_image_dir on success.
457 return True 517 """
518 if self.forced_image:
519 return self.GenerateUpdateImageWithCache(
520 self.forced_image,
521 static_image_dir=static_image_dir)
522 elif self.serve_only:
523 return self.GenerateImageFromZip(static_image_dir)
458 else: 524 else:
459 if self.forced_image: 525 if board_id:
460 has_built_image = self.GenerateUpdateImage( 526 return self.GenerateLatestUpdateImage(board_id,
461 self.forced_image, move_to_static_dir=True, 527 client_version,
462 static_image_dir=static_image_dir) 528 static_image_dir)
463 return has_built_image
464 elif self.serve_only:
465 return self.GenerateImageFromZip(static_image_dir)
466 else:
467 if board_id:
468 return self.GenerateLatestUpdateImage(board_id,
469 client_version,
470 static_image_dir)
471 529
472 _LogMessage('You must set --board for pre-generating latest update.') 530 _LogMessage('You must set --board for pre-generating latest update.')
473 return False 531 return None
474 532
475 def PreGenerateUpdate(self): 533 def PreGenerateUpdate(self):
476 """Pre-generates an update. Returns True on success.""" 534 """Pre-generates an update. Returns True on success.
535 """
477 # Does not work with factory config. 536 # Does not work with factory config.
478 assert(not self.factory_config) 537 assert(not self.factory_config)
479 _LogMessage('Pre-generating the update payload.') 538 _LogMessage('Pre-generating the update payload.')
480 # Does not work with labels so just use static dir. 539 # Does not work with labels so just use static dir.
481 if self.GenerateUpdatePayloadForNonFactory(self.board, '0.0.0.0', 540 if self.GenerateUpdatePayloadForNonFactory(self.board, '0.0.0.0',
482 self.static_dir): 541 self.static_dir):
483 # Force the devserver to use the pre-generated payload.
484 self.use_cached = True
485 _LogMessage('Pre-generated update successfully.') 542 _LogMessage('Pre-generated update successfully.')
486 return True 543 return True
487 else: 544 else:
488 _LogMessage('Failed to pre-generate update.') 545 _LogMessage('Failed to pre-generate update.')
489 return False 546 return False
490 547
491 def HandleUpdatePing(self, data, label=None): 548 def HandleUpdatePing(self, data, label=None):
492 """Handles an update ping from an update client. 549 """Handles an update ping from an update client.
493 550
494 Args: 551 Args:
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
536 593
537 # Separate logic as Factory requests have static url's that override 594 # Separate logic as Factory requests have static url's that override
538 # other options. 595 # other options.
539 if self.factory_config: 596 if self.factory_config:
540 return self.HandleFactoryRequest(board_id, channel) 597 return self.HandleFactoryRequest(board_id, channel)
541 else: 598 else:
542 static_image_dir = self.static_dir 599 static_image_dir = self.static_dir
543 if label: 600 if label:
544 static_image_dir = os.path.join(static_image_dir, label) 601 static_image_dir = os.path.join(static_image_dir, label)
545 602
546 if self.GenerateUpdatePayloadForNonFactory(board_id, client_version, 603 payload_path = self.GenerateUpdatePayloadForNonFactory(board_id,
547 static_image_dir): 604 client_version,
548 filename = os.path.join(static_image_dir, 'update.gz') 605 static_image_dir)
606 if payload_path:
607 filename = os.path.join(static_image_dir, payload_path)
549 hash = self._GetHash(filename) 608 hash = self._GetHash(filename)
550 sha256 = self._GetSHA256(filename) 609 sha256 = self._GetSHA256(filename)
551 size = self._GetSize(filename) 610 size = self._GetSize(filename)
552 is_delta_format = self._IsDeltaFormatFile(filename) 611 is_delta_format = self._IsDeltaFormatFile(filename)
553 if label: 612 if label:
554 url = '%s/%s/update.gz' % (static_urlbase, label) 613 url = '%s/%s/%s' % (static_urlbase, label, payload_path)
555 else: 614 else:
556 url = '%s/update.gz' % static_urlbase 615 url = '%s/%s' % (static_urlbase, payload_path)
557 616
558 _LogMessage('Responding to client to use url %s to get image.' % url) 617 _LogMessage('Responding to client to use url %s to get image.' % url)
559 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format) 618 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
560 else: 619 else:
561 return self.GetNoUpdatePayload() 620 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