blob: 87e31deca69ce5b8396fccb54d50688004b1e7fc [file] [log] [blame]
Darin Petkovc3fd90c2011-05-11 21:23:001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
[email protected]ded22402009-10-26 22:36:212# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
[email protected]ded22402009-10-26 22:36:215from xml.dom import minidom
Satoru Takabayashid733cbe2011-11-15 17:36:326import datetime
Dale Curtisc9aaf3a2011-08-09 22:47:407import json
[email protected]ded22402009-10-26 22:36:218import os
Darin Petkov798fe7d2010-03-22 22:18:139import shutil
Chris Sosa05491b12010-11-09 01:14:1610import subprocess
Darin Petkov2b2ff4b2010-07-27 22:02:0911import time
Gilad Arnold0c9c8602012-10-03 06:58:5812import urllib2
Don Garrett0ad09372010-12-07 00:20:3013import urlparse
Chris Sosa7c931362010-10-12 02:49:0114
Gilad Arnoldabb352e2012-09-23 08:24:2715import cherrypy
16
17from build_util import BuildObject
Gilad Arnold55a2a372012-10-02 16:46:3218import common_util
Gilad Arnoldc65330c2012-09-20 22:17:4819import log_util
Chris Sosa05491b12010-11-09 01:14:1620
Gilad Arnoldc65330c2012-09-20 22:17:4821
22# Module-local log function.
23def _Log(message, *args, **kwargs):
24 return log_util.LogWithTag('UPDATE', message, *args, **kwargs)
25
[email protected]ded22402009-10-26 22:36:2126
Chris Sosa417e55d2011-01-26 00:40:4827UPDATE_FILE = 'update.gz'
28STATEFUL_FILE = 'stateful.tgz'
29CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 22:46:2230
Don Garrett0ad09372010-12-07 00:20:3031
Gilad Arnold0c9c8602012-10-03 06:58:5832class AutoupdateError(Exception):
33 """Exception classes used by this module."""
34 pass
35
36
Don Garrett0ad09372010-12-07 00:20:3037def _ChangeUrlPort(url, new_port):
38 """Return the URL passed in with a different port"""
39 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
40 host_port = netloc.split(':')
41
42 if len(host_port) == 1:
43 host_port.append(new_port)
44 else:
45 host_port[1] = new_port
46
47 print host_port
48 netloc = "%s:%s" % tuple(host_port)
49
50 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
51
52
Gilad Arnold286a0062012-01-12 21:47:0253class HostInfo:
54 """Records information about an individual host.
55
56 Members:
57 attrs: Static attributes (legacy)
58 log: Complete log of recorded client entries
59 """
60
61 def __init__(self):
62 # A dictionary of current attributes pertaining to the host.
63 self.attrs = {}
64
65 # A list of pairs consisting of a timestamp and a dictionary of recorded
66 # attributes.
67 self.log = []
68
69 def __repr__(self):
70 return 'attrs=%s, log=%s' % (self.attrs, self.log)
71
72 def AddLogEntry(self, entry):
73 """Append a new log entry."""
74 # Append a timestamp.
75 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
76 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
77 # Add entry to hosts' message log.
78 self.log.append(entry)
79
80 def SetAttr(self, attr, value):
81 """Set an attribute value."""
82 self.attrs[attr] = value
83
84 def GetAttr(self, attr):
85 """Returns the value of an attribute."""
86 if attr in self.attrs:
87 return self.attrs[attr]
88
89 def PopAttr(self, attr, default):
90 """Returns and deletes a particular attribute."""
91 return self.attrs.pop(attr, default)
92
93
94class HostInfoTable:
95 """Records information about a set of hosts who engage in update activity.
96
97 Members:
98 table: Table of information on hosts.
99 """
100
101 def __init__(self):
102 # A dictionary of host information. Keys are normally IP addresses.
103 self.table = {}
104
105 def __repr__(self):
106 return '%s' % self.table
107
108 def GetInitHostInfo(self, host_id):
109 """Return a host's info object, or create a new one if none exists."""
110 return self.table.setdefault(host_id, HostInfo())
111
112 def GetHostInfo(self, host_id):
113 """Return an info object for given host, if such exists."""
114 if host_id in self.table:
115 return self.table[host_id]
116
117
[email protected]64244662009-11-12 00:52:08118class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 22:46:22119 """Class that contains functionality that handles Chrome OS update pings.
120
121 Members:
Gilad Arnold0c9c8602012-10-03 06:58:58122 serve_only: serve only pre-built updates. static_dir must contain
123 update.gz and stateful.tgz.
124 factory_config: path to the factory config file if handling factory
125 requests.
126 use_test_image: use chromiumos_test_image.bin rather than the standard.
127 urlbase: base URL, other than devserver, for update images.
128 forced_image: path to an image to use for all updates.
129 payload_path: path to pre-generated payload to serve.
130 src_image: if specified, creates a delta payload from this image.
131 proxy_port: port of local proxy to tell client to connect to you
132 through.
133 vm: set for VM images (doesn't patch kernel)
134 board: board for the image. Needed for pre-generating of updates.
135 copy_to_static_root: copies images generated from the cache to ~/static.
136 private_key: path to private key in PEM format.
137 critical_update: whether provisioned payload is critical.
138 remote_payload: whether provisioned payload is remotely staged.
Chris Sosa0356d3b2010-09-16 22:46:22139 """
[email protected]ded22402009-10-26 22:36:21140
Gilad Arnold0c9c8602012-10-03 06:58:58141 _PAYLOAD_URL_PREFIX = '/static/'
142 _FILEINFO_URL_PREFIX = '/api/fileinfo/'
143
Sean O'Connor1f7fd362010-04-07 23:34:52144 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 21:15:23145 factory_config_path=None,
Gilad Arnold0c9c8602012-10-03 06:58:58146 forced_image=None, payload_path=None,
147 proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-15 00:33:22148 copy_to_static_root=True, private_key=None,
Gilad Arnold0c9c8602012-10-03 06:58:58149 critical_update=False, remote_payload=False,
Chris Sosae67b78f12010-11-05 00:33:16150 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-21 06:23:48151 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 23:34:52152 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-03 00:37:32153 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 22:46:22154 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 23:54:41155 if urlbase:
Chris Sosa9841e1c2010-10-14 17:51:45156 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 23:54:41157 else:
Chris Sosa9841e1c2010-10-14 17:51:45158 self.urlbase = None
Chris Sosa5d342a22010-09-28 23:54:41159
Chris Sosa0356d3b2010-09-16 22:46:22160 self.forced_image = forced_image
Gilad Arnold0c9c8602012-10-03 06:58:58161 self.payload_path = payload_path
Chris Sosa62f720b2010-10-27 04:39:48162 self.src_image = src_image
Don Garrett0ad09372010-12-07 00:20:30163 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-29 06:42:37164 self.vm = vm
Chris Sosae67b78f12010-11-05 00:33:16165 self.board = board
Chris Sosa08d55a22011-01-20 00:08:02166 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-15 00:33:22167 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 17:36:32168 self.critical_update = critical_update
Gilad Arnold0c9c8602012-10-03 06:58:58169 self.remote_payload = remote_payload
Don Garrettfff4c322010-11-19 21:37:12170
Chris Sosa417e55d2011-01-26 00:40:48171 # Path to pre-generated file.
172 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-21 06:23:48173
Dale Curtisc9aaf3a2011-08-09 22:47:40174 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 21:47:02175 # information about a given host. A host is identified by its IP address.
176 # The info stored for each host includes a complete log of events for this
177 # host, as well as a dictionary of current attributes derived from events.
178 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 22:47:40179
Chris Sosa0356d3b2010-09-16 22:46:22180 def _GetSecondsSinceMidnight(self):
181 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 22:02:09182 now = time.localtime()
183 return now[3] * 3600 + now[4] * 60 + now[5]
184
Chris Sosa0356d3b2010-09-16 22:46:22185 def _GetDefaultBoardID(self):
186 """Returns the default board id stored in .default_board."""
187 board_file = '%s/.default_board' % (self.scripts_dir)
188 try:
189 return open(board_file).read()
190 except IOError:
191 return 'x86-generic'
192
193 def _GetLatestImageDir(self, board_id):
194 """Returns the latest image dir based on shell script."""
195 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
196 return os.popen(cmd).read().strip()
197
198 def _GetVersionFromDir(self, image_dir):
199 """Returns the version of the image based on the name of the directory."""
200 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 15:52:52201 parts = latest_version.split('-')
202 if len(parts) == 2:
203 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
204 # TODO(derat): Remove the code for old-style versions after 20120101.
205 return parts[0]
206 else:
207 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
208 return parts[1]
Chris Sosa0356d3b2010-09-16 22:46:22209
210 def _CanUpdate(self, client_version, latest_version):
Don Garrettf90edf02010-11-17 01:36:14211 """Returns true if the latest_version is greater than the client_version.
212 """
Gilad Arnoldc65330c2012-09-20 22:17:48213 _Log('client version %s latest version %s'
214 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 15:52:52215
216 client_tokens = client_version.replace('_', '').split('.')
217 # If the client has an old four-token version like "0.16.892.0", drop the
218 # first two tokens -- we use versions like "892.0.0" now.
219 # TODO(derat): Remove the code for old-style versions after 20120101.
220 if len(client_tokens) == 4:
221 client_tokens = client_tokens[2:]
222
223 latest_tokens = latest_version.replace('_', '').split('.')
224 if len(latest_tokens) == 4:
225 latest_tokens = latest_tokens[2:]
226
227 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 22:46:22228 if int(latest_tokens[i]) == int(client_tokens[i]):
229 continue
230 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 15:52:52231
232 # Favor four-token new-style versions on the server over old-style versions
233 # on the client if everything else matches.
234 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 22:46:22235
Chris Sosa0356d3b2010-09-16 22:46:22236 def _UnpackZip(self, image_dir):
237 """Unpacks an image.zip into a given directory."""
238 image = os.path.join(image_dir, self._GetImageName())
239 if os.path.exists(image):
240 return True
241 else:
242 # -n, never clobber an existing file, in case we get invoked
243 # simultaneously by multiple request handlers. This means that
244 # we're assuming each image.zip file lives in a versioned
245 # directory (a la Buildbot).
246 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
247
248 def _GetImageName(self):
249 """Returns the name of the image that should be used."""
250 if self.use_test_image:
251 image_name = 'chromiumos_test_image.bin'
252 else:
253 image_name = 'chromiumos_image.bin'
254 return image_name
255
Andrew de los Reyes5679b972010-10-26 00:34:49256 def _IsDeltaFormatFile(self, filename):
257 try:
258 file_handle = open(filename, 'r')
259 delta_magic = 'CrAU'
260 magic = file_handle.read(len(delta_magic))
261 return magic == delta_magic
262 except Exception:
263 return False
264
Gilad Arnold0c9c8602012-10-03 06:58:58265 def GetUpdatePayload(self, sha1, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 22:46:22266 """Returns a payload to the client corresponding to a new update.
267
268 Args:
Gilad Arnold0c9c8602012-10-03 06:58:58269 sha1: SHA1 hash of update blob
270 sha256: SHA256 hash of update blob
Chris Sosa0356d3b2010-09-16 22:46:22271 size: size of update blob
272 url: where to find update blob
273 Returns:
274 Xml string to be passed back to client.
275 """
Andrew de los Reyes5679b972010-10-26 00:34:49276 delta = 'false'
277 if is_delta_format:
278 delta = 'true'
[email protected]21a5ca32009-11-04 18:23:23279 payload = """<?xml version="1.0" encoding="UTF-8"?>
280 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 22:02:09281 <daystart elapsed_seconds="%s"/>
[email protected]21a5ca32009-11-04 18:23:23282 <app appid="{%s}" status="ok">
283 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-21 06:23:48284 <updatecheck
Jay Srinivasan9a1c4572012-03-17 02:16:58285 ChromeOSVersion="9999.0.0"
Sean O'Connor14b6a0a2010-03-21 06:23:48286 codebase="%s"
287 hash="%s"
Darin Petkov91436cb2010-09-28 15:52:17288 sha256="%s"
Sean O'Connor14b6a0a2010-03-21 06:23:48289 needsadmin="false"
290 size="%s"
Andrew de los Reyes5679b972010-10-26 00:34:49291 IsDelta="%s"
Satoru Takabayashid733cbe2011-11-15 17:36:32292 status="ok"
293 %s/>
[email protected]21a5ca32009-11-04 18:23:23294 </app>
295 </gupdate>
296 """
Satoru Takabayashid733cbe2011-11-15 17:36:32297 extra_attributes = []
298 if self.critical_update:
299 # The date string looks like '20111115' (2011-11-15). As of writing,
300 # there's no particular format for the deadline value that the
301 # client expects -- it's just empty vs. non-empty.
302 date_str = datetime.date.today().strftime('%Y%m%d')
303 extra_attributes.append('deadline="%s"' % date_str)
304 xml = payload % (self._GetSecondsSinceMidnight(),
Gilad Arnold0c9c8602012-10-03 06:58:58305 self.app_id, url, sha1, sha256, size, delta,
Satoru Takabayashid733cbe2011-11-15 17:36:32306 ' '.join(extra_attributes))
Gilad Arnoldc65330c2012-09-20 22:17:48307 _Log('Generated update payload: %s' % xml)
Satoru Takabayashid733cbe2011-11-15 17:36:32308 return xml
[email protected]ded22402009-10-26 22:36:21309
[email protected]21a5ca32009-11-04 18:23:23310 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 22:46:22311 """Returns a payload to the client corresponding to no update."""
Darin Petkov845f1172011-01-05 22:45:24312 payload = """<?xml version="1.0" encoding="UTF-8"?>
313 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
314 <daystart elapsed_seconds="%s"/>
315 <app appid="{%s}" status="ok">
316 <ping status="ok"/>
317 <updatecheck status="noupdate"/>
318 </app>
319 </gupdate>
[email protected]21a5ca32009-11-04 18:23:23320 """
Chris Sosa0356d3b2010-09-16 22:46:22321 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
[email protected]ded22402009-10-26 22:36:21322
Don Garrettf90edf02010-11-17 01:36:14323 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 22:46:22324 """Generates an update gz given a full path to an image.
325
326 Args:
327 image_path: Full path to image.
328 Returns:
329 Path to created update_payload or None on error.
330 """
Don Garrettfff4c322010-11-19 21:37:12331 update_path = os.path.join(output_dir, UPDATE_FILE)
Gilad Arnoldc65330c2012-09-20 22:17:48332 _Log('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 22:46:22333
Chris Sosa0f1ec842011-02-15 00:33:22334 update_command = [
Chris Sosa5b8b5eb2012-03-27 18:15:27335 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-15 00:33:22336 '--image="%s"' % image_path,
337 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-15 00:33:22338 ]
Chris Sosa4136e692010-10-29 06:42:37339
Chris Sosa0f1ec842011-02-15 00:33:22340 if src_image: update_command.append('--src_image="%s"' % src_image)
341 if not self.vm: update_command.append('--patch_kernel')
342 if self.private_key: update_command.append('--private_key="%s"' %
343 self.private_key)
344
345 update_string = ' '.join(update_command)
Gilad Arnoldc65330c2012-09-20 22:17:48346 _Log('Running ' + update_string)
Chris Sosa0f1ec842011-02-15 00:33:22347 if os.system(update_string) != 0:
Gilad Arnoldc65330c2012-09-20 22:17:48348 _Log('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 22:46:22349 return None
350
Don Garrettfff4c322010-11-19 21:37:12351 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 22:46:22352
Don Garrettf90edf02010-11-17 01:36:14353 def GenerateStatefulFile(self, image_path, output_dir):
354 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 22:46:22355
356 Args:
357 image_path: Full path to image.
358 Returns:
Don Garrettf90edf02010-11-17 01:36:14359 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-11 01:31:18360 Raises:
361 A subprocess exception if the update generator fails to generate a
362 stateful payload.
Chris Sosa0356d3b2010-09-16 22:46:22363 """
Don Garrettfff4c322010-11-19 21:37:12364 output_gz = os.path.join(output_dir, STATEFUL_FILE)
Chris Sosa908fd6f2010-11-11 01:31:18365 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 18:15:27366 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-11 01:31:18367 '--image=%s' % image_path,
Don Garrettf90edf02010-11-17 01:36:14368 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-11 01:31:18369 ])
Don Garrettfff4c322010-11-19 21:37:12370 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 22:46:22371
Don Garrettf90edf02010-11-17 01:36:14372 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
373 """Find directory to store a cached update.
374
Gilad Arnold55a2a372012-10-02 16:46:32375 Given one, or two images for an update, this finds which cache directory
376 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-17 01:36:14377
Gilad Arnold55a2a372012-10-02 16:46:32378 Returns:
379 A directory path for storing a cached update, of the following form:
380 Non-delta updates:
381 CACHE_DIR/<dest_hash>
382 Delta updates:
383 CACHE_DIR/<src_hash>_<dest_hash>
384 Signed updates (self.private_key):
385 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-08 02:32:50386 """
Gilad Arnold55a2a372012-10-02 16:46:32387 update_dir = ''
Chris Sosa744e1472011-09-08 02:32:50388 if src_image:
Gilad Arnold55a2a372012-10-02 16:46:32389 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-17 01:36:14390
Gilad Arnold55a2a372012-10-02 16:46:32391 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-08 02:32:50392 if self.private_key:
Gilad Arnold55a2a372012-10-02 16:46:32393 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-08 02:32:50394
Chris Sosa9fba7562012-01-31 18:15:47395 if not self.vm:
Gilad Arnold55a2a372012-10-02 16:46:32396 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 18:15:47397
Gilad Arnold55a2a372012-10-02 16:46:32398 return os.path.join(CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-17 01:36:14399
Don Garrettfff4c322010-11-19 21:37:12400 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-17 01:36:14401 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 22:46:22402
Chris Sosade91f672010-11-16 18:05:44403 Args:
Don Garrettf90edf02010-11-17 01:36:14404 src_image: image we are updating from (Null/empty for non-delta)
405 image_path: full path to the image.
406 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 18:05:44407 Returns:
Don Garrettfff4c322010-11-19 21:37:12408 update payload name relative to output_dir
Chris Sosade91f672010-11-16 18:05:44409 """
Don Garrettf90edf02010-11-17 01:36:14410 update_file = None
411 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 17:29:43412
Don Garrettf90edf02010-11-17 01:36:14413 # Actually do the generation
Gilad Arnoldc65330c2012-09-20 22:17:48414 _Log('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 21:37:12415 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-17 01:36:14416 image_path,
417 output_dir)
[email protected]ded22402009-10-26 22:36:21418
Don Garrettf90edf02010-11-17 01:36:14419 if update_file:
420 stateful_update_file = self.GenerateStatefulFile(image_path,
421 output_dir)
422
423 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 21:37:12424 return update_file
Chris Sosa417e55d2011-01-26 00:40:48425 else:
Gilad Arnoldc65330c2012-09-20 22:17:48426 _Log('Failed to generate update.')
Chris Sosa417e55d2011-01-26 00:40:48427 return None
Don Garrettf90edf02010-11-17 01:36:14428
429 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
430 """Force generates an update payload based on the given image_path.
[email protected]ded22402009-10-26 22:36:21431
Chris Sosa0356d3b2010-09-16 22:46:22432 Args:
433 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 22:46:22434 static_image_dir: the directory to move images to after generating.
435 Returns:
Don Garrettf90edf02010-11-17 01:36:14436 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-26 00:40:48437 or None.
Chris Sosa0356d3b2010-09-16 22:46:22438 """
Gilad Arnoldc65330c2012-09-20 22:17:48439 _Log('Generating update for src %s image %s' % (self.src_image, image_path))
Chris Sosae67b78f12010-11-05 00:33:16440
Chris Sosa417e55d2011-01-26 00:40:48441 # If it was pregenerated_path, don't regenerate
442 if self.pregenerated_path:
443 return self.pregenerated_path
Don Garrettfff4c322010-11-19 21:37:12444
Don Garrettf90edf02010-11-17 01:36:14445 # Which sub_dir of static_image_dir should hold our cached update image
446 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Gilad Arnoldc65330c2012-09-20 22:17:48447 _Log('Caching in sub_dir "%s"' % cache_sub_dir)
Don Garrettf90edf02010-11-17 01:36:14448
Chris Sosa417e55d2011-01-26 00:40:48449 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
450
Don Garrettf90edf02010-11-17 01:36:14451 # The cached payloads exist in a cache dir
452 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-26 00:40:48453 update_path)
Don Garrettf90edf02010-11-17 01:36:14454 cache_stateful_payload = os.path.join(static_image_dir,
455 cache_sub_dir,
Don Garrettfff4c322010-11-19 21:37:12456 STATEFUL_FILE)
Don Garrettf90edf02010-11-17 01:36:14457
Chris Sosa417e55d2011-01-26 00:40:48458 # Check to see if this cache directory is valid.
459 if not os.path.exists(cache_update_payload) or not os.path.exists(
460 cache_stateful_payload):
Don Garrettf90edf02010-11-17 01:36:14461 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-26 00:40:48462 # Clean up stale state.
463 os.system('rm -rf "%s"' % full_cache_dir)
464 os.makedirs(full_cache_dir)
465 return_path = self.GenerateUpdateImage(image_path,
466 full_cache_dir)
Don Garrettf90edf02010-11-17 01:36:14467
Chris Sosa417e55d2011-01-26 00:40:48468 # Clean up cache dir since it's not valid.
469 if not return_path:
470 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-17 01:36:14471 return None
Chris Sosa417e55d2011-01-26 00:40:48472
473 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-17 01:36:14474
Chris Sosa08d55a22011-01-20 00:08:02475 # Generation complete, copy if requested.
476 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-26 00:40:48477 # The final results exist directly in static
478 update_payload = os.path.join(static_image_dir,
479 UPDATE_FILE)
480 stateful_payload = os.path.join(static_image_dir,
481 STATEFUL_FILE)
Gilad Arnold55a2a372012-10-02 16:46:32482 common_util.CopyFile(cache_update_payload, update_payload)
483 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-26 00:40:48484 return UPDATE_FILE
485 else:
486 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 22:46:22487
488 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-17 01:36:14489 static_image_dir):
Chris Sosa0356d3b2010-09-16 22:46:22490 """Generates an update using the latest image that has been built.
491
492 This will only generate an update if the newest update is newer than that
493 on the client or client_version is 'ForcedUpdate'.
494
495 Args:
496 board_id: Name of the board.
497 client_version: Current version of the client or 'ForcedUpdate'
498 static_image_dir: the directory to move images to after generating.
499 Returns:
Don Garrettf90edf02010-11-17 01:36:14500 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 22:46:22501 """
502 latest_image_dir = self._GetLatestImageDir(board_id)
503 latest_version = self._GetVersionFromDir(latest_image_dir)
504 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
505
Gilad Arnoldc65330c2012-09-20 22:17:48506 _Log('Preparing to generate update from latest built image %s.' %
507 latest_image_path)
Chris Sosa0356d3b2010-09-16 22:46:22508
509 # Check to see whether or not we should update.
510 if client_version != 'ForcedUpdate' and not self._CanUpdate(
511 client_version, latest_version):
Gilad Arnoldc65330c2012-09-20 22:17:48512 _Log('no update')
Don Garrettf90edf02010-11-17 01:36:14513 return None
Chris Sosa0356d3b2010-09-16 22:46:22514
Don Garrettf90edf02010-11-17 01:36:14515 return self.GenerateUpdateImageWithCache(latest_image_path,
516 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 22:46:22517
Andrew de los Reyes52620802010-04-12 20:40:07518 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
519 """Imports a factory-floor server configuration file. The file should
520 be in this format:
521 config = [
522 {
523 'qual_ids': set([1, 2, 3, "x86-generic"]),
524 'factory_image': 'generic-factory.gz',
525 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
526 'release_image': 'generic-release.gz',
527 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
528 'oempartitionimg_image': 'generic-oem.gz',
529 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-20 05:17:08530 'efipartitionimg_image': 'generic-efi.gz',
531 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 20:40:07532 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 03:44:26533 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 01:56:15534 'firmware_image': 'generic-firmware.gz',
535 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 20:40:07536 },
537 {
538 'qual_ids': set([6]),
539 'factory_image': '6-factory.gz',
540 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
541 'release_image': '6-release.gz',
542 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
543 'oempartitionimg_image': '6-oem.gz',
544 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-20 05:17:08545 'efipartitionimg_image': '6-efi.gz',
546 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 20:40:07547 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 03:44:26548 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 01:56:15549 'firmware_image': '6-firmware.gz',
550 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 20:40:07551 },
552 ]
553 The server will look for the files by name in the static files
554 directory.
Chris Sosaa73ec162010-05-04 03:18:02555
Andrew de los Reyes52620802010-04-12 20:40:07556 If validate_checksums is True, validates checksums and exits. If
557 a checksum mismatch is found, it's printed to the screen.
558 """
559 f = open(filename, 'r')
560 output = {}
561 exec(f.read(), output)
562 self.factory_config = output['config']
563 success = True
564 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 03:44:26565 for key in stanza.copy().iterkeys():
566 suffix = '_image'
567 if key.endswith(suffix):
568 kind = key[:-len(suffix)]
Gilad Arnold55a2a372012-10-02 16:46:32569 stanza[kind + '_size'] = common_util.GetFileSize(os.path.join(
Chris Sosa0356d3b2010-09-16 22:46:22570 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 03:44:26571 if validate_checksums:
Gilad Arnold55a2a372012-10-02 16:46:32572 factory_checksum = common_util.GetFileSha1(
573 os.path.join(self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 03:44:26574 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 22:46:22575 print ('Error: checksum mismatch for %s. Expected "%s" but file '
576 'has checksum "%s".' % (stanza[kind + '_image'],
577 stanza[kind + '_checksum'],
578 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 03:44:26579 success = False
Chris Sosa0356d3b2010-09-16 22:46:22580
Andrew de los Reyes52620802010-04-12 20:40:07581 if validate_checksums:
582 if success is False:
Gilad Arnold0c9c8602012-10-03 06:58:58583 raise AutoupdateError('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 22:46:22584
Andrew de los Reyes52620802010-04-12 20:40:07585 print 'Config file looks good.'
586
587 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 12:18:41588 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 20:40:07589 for stanza in self.factory_config:
590 if board_id not in stanza['qual_ids']:
591 continue
Nick Sanders15cd6ae2010-06-30 19:30:56592 if kind + '_image' not in stanza:
593 break
Andrew de los Reyes52620802010-04-12 20:40:07594 return (stanza[kind + '_image'],
595 stanza[kind + '_checksum'],
596 stanza[kind + '_size'])
Gilad Arnold0c9c8602012-10-03 06:58:58597 return None, None, None
[email protected]ded22402009-10-26 22:36:21598
Chris Sosa7c931362010-10-12 02:49:01599 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 22:46:22600 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
601 if filename is None:
Gilad Arnoldc65330c2012-09-20 22:17:48602 _Log('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 22:46:22603 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-15 01:01:52604 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-26 00:34:49605 is_delta_format = self._IsDeltaFormatFile(filename)
Gilad Arnoldc65330c2012-09-20 22:17:48606 _Log('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 15:52:17607 # Factory install is using memento updater which is using the sha-1 hash so
608 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-26 00:34:49609 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 22:46:22610
Chris Sosa151643e2010-10-28 21:40:57611 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
612 static_image_dir):
Don Garrettf90edf02010-11-17 01:36:14613 """Generates an update for non-factory image.
Don Garrett710470d2010-11-16 01:43:44614
Don Garrettf90edf02010-11-17 01:36:14615 Returns:
616 file name relative to static_image_dir on success.
617 """
Dale Curtis723ec472010-11-30 22:06:47618 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
619 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
620
Gilad Arnold0c9c8602012-10-03 06:58:58621 if self.payload_path:
Don Garrett0c880e22010-11-18 02:13:37622 # If the forced payload is not already in our static_image_dir,
623 # copy it there.
Gilad Arnold0c9c8602012-10-03 06:58:58624 src_path = os.path.abspath(self.payload_path)
Don Garrettee25e552010-11-23 20:09:35625 src_stateful = os.path.join(os.path.dirname(src_path),
626 STATEFUL_FILE)
Don Garrettee25e552010-11-23 20:09:35627
628 # Only copy the files if the source directory is different from dest.
629 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 16:46:32630 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 20:09:35631
632 # The stateful payload is optional.
633 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 16:46:32634 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 20:09:35635 else:
Gilad Arnoldc65330c2012-09-20 22:17:48636 _Log('WARN: %s not found. Expected for dev and test builds.' %
637 STATEFUL_FILE)
Don Garrettee25e552010-11-23 20:09:35638 if os.path.exists(dest_stateful):
639 os.remove(dest_stateful)
Don Garrett0c880e22010-11-18 02:13:37640
Don Garrettfff4c322010-11-19 21:37:12641 return UPDATE_FILE
Don Garrett0c880e22010-11-18 02:13:37642 elif self.forced_image:
Don Garrettf90edf02010-11-17 01:36:14643 return self.GenerateUpdateImageWithCache(
644 self.forced_image,
645 static_image_dir=static_image_dir)
646 elif self.serve_only:
Dale Curtis723ec472010-11-30 22:06:47647 # Warn if update or stateful files can't be found.
648 if not os.path.exists(dest_path):
Gilad Arnoldc65330c2012-09-20 22:17:48649 _Log('WARN: %s not found. Expected for dev and test builds.' %
650 UPDATE_FILE)
Dale Curtis723ec472010-11-30 22:06:47651
652 if not os.path.exists(dest_stateful):
Gilad Arnoldc65330c2012-09-20 22:17:48653 _Log('WARN: %s not found. Expected for dev and test builds.' %
654 STATEFUL_FILE)
Dale Curtis723ec472010-11-30 22:06:47655
656 return UPDATE_FILE
Don Garrettf90edf02010-11-17 01:36:14657 else:
658 if board_id:
659 return self.GenerateLatestUpdateImage(board_id,
660 client_version,
661 static_image_dir)
662
Gilad Arnoldc65330c2012-09-20 22:17:48663 _Log('Failed to genereate update. '
664 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-17 01:36:14665 return None
Chris Sosa2c048f12010-10-27 23:05:27666
667 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-26 00:40:48668 """Pre-generates an update and prints out the relative path it.
669
670 Returns relative path of the update on success.
Don Garrettf90edf02010-11-17 01:36:14671 """
Chris Sosa2c048f12010-10-27 23:05:27672 # Does not work with factory config.
673 assert(not self.factory_config)
Gilad Arnoldc65330c2012-09-20 22:17:48674 _Log('Pre-generating the update payload.')
Chris Sosa2c048f12010-10-27 23:05:27675 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-26 00:40:48676 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
677 self.board, '0.0.0.0', self.static_dir)
678 if pregenerated_update:
679 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
680
681 return pregenerated_update
Chris Sosa2c048f12010-10-27 23:05:27682
Gilad Arnold0c9c8602012-10-03 06:58:58683 def _GetRemotePayloadAttrs(self, url):
684 """Returns hashes, size and delta flag of a remote update payload.
685
686 Obtain attributes of a payload file available on a remote devserver. This
687 is based on the assumption that the payload URL uses the /static prefix. We
688 need to make sure that both clients (requests) and remote devserver
689 (provisioning) preserve this invariant.
690
691 Args:
692 url: URL of statically staged remote file (http://host:port/static/...)
693 Returns:
694 A tuple containing the SHA1, SHA256, file size and whether or not it's a
695 delta payload (Boolean).
696 """
697 if self._PAYLOAD_URL_PREFIX not in url:
698 raise AutoupdateError(
699 'Payload URL does not have the expected prefix (%s)' %
700 self._PAYLOAD_URL_PREFIX)
701 fileinfo_url = url.replace(self._PAYLOAD_URL_PREFIX,
702 self._FILEINFO_URL_PREFIX)
703 _Log('retrieving file info for remote payload via %s' % fileinfo_url)
704 try:
705 conn = urllib2.urlopen(fileinfo_url)
706 file_attr_dict = json.loads(conn.read())
707 sha1 = file_attr_dict['sha1']
708 sha256 = file_attr_dict['sha256']
709 size = file_attr_dict['size']
710 except Exception, e:
711 _Log('failed to obtain remote payload info: %s' % str(e))
712 raise
713 is_delta_format = ('_mton' in url) or ('_nton' in url)
714
715 return sha1, sha256, size, is_delta_format
716
717 def _GetLocalPayloadAttrs(self, static_image_dir, payload_path):
718 """Returns hashes, size and delta flag of a local update payload.
719
720 Args:
721 static_image_dir: directory where static files are being staged
722 payload_path: path to the payload file inside the static directory
723 Returns:
724 A tuple containing the SHA1, SHA256, file size and whether or not it's a
725 delta payload (Boolean).
726 """
727 filename = os.path.join(static_image_dir, payload_path)
728 sha1 = common_util.GetFileSha1(filename)
729 sha256 = common_util.GetFileSha256(filename)
730 size = common_util.GetFileSize(filename)
731 is_delta_format = self._IsDeltaFormatFile(filename)
732 return sha1, sha256, size, is_delta_format
733
Sean O'Connor14b6a0a2010-03-21 06:23:48734 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 22:46:22735 """Handles an update ping from an update client.
736
737 Args:
738 data: xml blob from client.
739 label: optional label for the update.
740 Returns:
741 Update payload message for client.
742 """
Chris Sosa9841e1c2010-10-14 17:51:45743 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 23:26:10744 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
745 # be in X-Forwarded-Host.
746 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
747 if x_forwarded_host:
748 self.hostname = 'http://' + x_forwarded_host
749 else:
750 self.hostname = cherrypy.request.base
751
Chris Sosa9841e1c2010-10-14 17:51:45752 if self.urlbase:
753 static_urlbase = self.urlbase
754 elif self.serve_only:
755 static_urlbase = '%s/static/archive' % self.hostname
756 else:
757 static_urlbase = '%s/static' % self.hostname
758
Don Garrett0ad09372010-12-07 00:20:30759 # If we have a proxy port, adjust the URL we instruct the client to
760 # use to go through the proxy.
761 if self.proxy_port:
762 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
763
Gilad Arnoldc65330c2012-09-20 22:17:48764 _Log('Using static url base %s' % static_urlbase)
765 _Log('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 22:46:22766
Chris Sosa9841e1c2010-10-14 17:51:45767 update_dom = minidom.parseString(data)
768 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 22:46:22769
Dale Curtisc9aaf3a2011-08-09 22:47:40770 # Determine request IP, strip any IPv6 data for simplicity.
771 client_ip = cherrypy.request.remote.ip.split(':')[-1]
772
Gilad Arnold286a0062012-01-12 21:47:02773 # Obtain (or init) info object for this client.
774 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
775
776 # Initialize an empty dictionary for event attributes.
777 log_message = {}
Dale Curtisc9aaf3a2011-08-09 22:47:40778
779 # Store event details in the host info dictionary for API usage.
780 event = root.getElementsByTagName('o:event')
781 if event:
Gilad Arnold286a0062012-01-12 21:47:02782 event_result = int(event[0].getAttribute('eventresult'))
783 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 22:33:21784 client_previous_version = (event[0].getAttribute('previousversion')
785 if event[0].hasAttribute('previousversion')
786 else None)
Gilad Arnold286a0062012-01-12 21:47:02787 # Store attributes to legacy host info structure
788 curr_host_info.attrs['last_event_status'] = event_result
789 curr_host_info.attrs['last_event_type'] = event_type
790 # Add attributes to log message
791 log_message['event_result'] = event_result
792 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 22:33:21793 if client_previous_version is not None:
794 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 21:47:02795
796 # Get information about the requester.
797 query = root.getElementsByTagName('o:app')[0]
798 if query:
799 client_version = query.getAttribute('version')
800 channel = query.getAttribute('track')
801 board_id = (query.hasAttribute('board') and query.getAttribute('board')
802 or self._GetDefaultBoardID())
803 # Add attributes to log message
804 log_message['version'] = client_version
805 log_message['track'] = channel
806 log_message['board'] = board_id
807
808 # Log client's message
809 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 22:47:40810
Chris Sosa0356d3b2010-09-16 22:46:22811 # We only generate update payloads for updatecheck requests.
812 update_check = root.getElementsByTagName('o:updatecheck')
813 if not update_check:
Gilad Arnoldc65330c2012-09-20 22:17:48814 _Log('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 22:46:22815 # TODO(sosa): Generate correct non-updatecheck payload to better test
816 # update clients.
817 return self.GetNoUpdatePayload()
818
Dale Curtisc9aaf3a2011-08-09 22:47:40819 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 21:47:02820 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 22:47:40821
822 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 21:47:02823 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 22:47:40824 if forced_update:
825 label = forced_update
826
Chris Sosa0356d3b2010-09-16 22:46:22827 # Separate logic as Factory requests have static url's that override
828 # other options.
Andrew de los Reyes52620802010-04-12 20:40:07829 if self.factory_config:
Chris Sosa7c931362010-10-12 02:49:01830 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 12:18:41831 else:
Gilad Arnold0c9c8602012-10-03 06:58:58832 url = ''
833 # Are we provisioning a remote or local payload?
834 if self.remote_payload:
835 # If no explicit label was provided, use the value of --payload.
836 if not label and self.payload_path:
837 label = self.payload_path
Chris Sosa0356d3b2010-09-16 22:46:22838
Gilad Arnold0c9c8602012-10-03 06:58:58839 # Form the URL of the update payload. This assumes that the payload
840 # file name is a devserver constant (which currently is the case).
841 url = '/'.join(filter(None, [static_urlbase, label, UPDATE_FILE]))
Chris Sosa5d342a22010-09-28 23:54:41842
Gilad Arnold0c9c8602012-10-03 06:58:58843 # Get remote payload attributes.
844 sha1, sha256, file_size, is_delta_format = \
845 self._GetRemotePayloadAttrs(url)
846 else:
847 # Generate payload.
848 static_image_dir = os.path.join(*filter(None, [self.static_dir, label]))
849 payload_path = self.GenerateUpdatePayloadForNonFactory(
850 board_id, client_version, static_image_dir)
851 # If properly generated, obtain the payload URL and attributes.
852 if payload_path:
853 url = '/'.join(filter(None, [static_urlbase, label, payload_path]))
854 sha1, sha256, file_size, is_delta_format = \
855 self._GetLocalPayloadAttrs(static_image_dir, payload_path)
856
857 # If we end up with an actual payload path, generate a response.
858 if url:
Gilad Arnoldc65330c2012-09-20 22:17:48859 _Log('Responding to client to use url %s to get image.' % url)
Gilad Arnold0c9c8602012-10-03 06:58:58860 return self.GetUpdatePayload(
861 sha1, sha256, file_size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 22:46:22862 else:
Nick Sanders723f3262010-09-16 12:18:41863 return self.GetNoUpdatePayload()
Dale Curtisc9aaf3a2011-08-09 22:47:40864
865 def HandleHostInfoPing(self, ip):
866 """Returns host info dictionary for the given IP in JSON format."""
867 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 21:47:02868 if ip in self.host_infos.table:
869 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
870
871 def HandleHostLogPing(self, ip):
872 """Returns a complete log of events for host in JSON format."""
873 if ip == 'all':
874 return json.dumps(
875 dict([(key, self.host_infos.table[key].log)
876 for key in self.host_infos.table]))
877 if ip in self.host_infos.table:
878 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 22:47:40879
880 def HandleSetUpdatePing(self, ip, label):
881 """Sets forced_update_label for a given host."""
882 assert ip, 'No ip provided.'
883 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 21:47:02884 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label