blob: fb8d2882c0bcac5f6a34f02149e8809febfb0028 [file] [log] [blame]
Darin Petkovc3fd90c2011-05-11 14:23:00 -07001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07005import json
rtc@google.comded22402009-10-26 22:36:21 +00006import os
Chris Sosa05491b12010-11-08 17:14:16 -08007import subprocess
Darin Petkov2b2ff4b2010-07-27 15:02:09 -07008import time
Gilad Arnold0c9c8602012-10-02 23:58:58 -07009import urllib2
Don Garrett0ad09372010-12-06 16:20:30 -080010import urlparse
Chris Sosa7c931362010-10-11 19:49:01 -070011
Gilad Arnoldabb352e2012-09-23 01:24:27 -070012import cherrypy
13
14from build_util import BuildObject
Chris Sosa52148582012-11-15 15:35:58 -080015import autoupdate_lib
Gilad Arnold55a2a372012-10-02 09:46:32 -070016import common_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070017import log_util
Chris Sosa05491b12010-11-08 17:14:16 -080018
Gilad Arnoldc65330c2012-09-20 15:17:48 -070019
20# Module-local log function.
21def _Log(message, *args, **kwargs):
22 return log_util.LogWithTag('UPDATE', message, *args, **kwargs)
23
rtc@google.comded22402009-10-26 22:36:21 +000024
Chris Sosa417e55d2011-01-25 16:40:48 -080025UPDATE_FILE = 'update.gz'
26STATEFUL_FILE = 'stateful.tgz'
27CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 15:46:22 -070028
Don Garrett0ad09372010-12-06 16:20:30 -080029
Gilad Arnold0c9c8602012-10-02 23:58:58 -070030class AutoupdateError(Exception):
31 """Exception classes used by this module."""
32 pass
33
34
Don Garrett0ad09372010-12-06 16:20:30 -080035def _ChangeUrlPort(url, new_port):
36 """Return the URL passed in with a different port"""
37 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
38 host_port = netloc.split(':')
39
40 if len(host_port) == 1:
41 host_port.append(new_port)
42 else:
43 host_port[1] = new_port
44
45 print host_port
46 netloc = "%s:%s" % tuple(host_port)
47
48 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
49
50
Gilad Arnold286a0062012-01-12 13:47:02 -080051class HostInfo:
52 """Records information about an individual host.
53
54 Members:
55 attrs: Static attributes (legacy)
56 log: Complete log of recorded client entries
57 """
58
59 def __init__(self):
60 # A dictionary of current attributes pertaining to the host.
61 self.attrs = {}
62
63 # A list of pairs consisting of a timestamp and a dictionary of recorded
64 # attributes.
65 self.log = []
66
67 def __repr__(self):
68 return 'attrs=%s, log=%s' % (self.attrs, self.log)
69
70 def AddLogEntry(self, entry):
71 """Append a new log entry."""
72 # Append a timestamp.
73 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
74 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
75 # Add entry to hosts' message log.
76 self.log.append(entry)
77
Gilad Arnold286a0062012-01-12 13:47:02 -080078
79class HostInfoTable:
80 """Records information about a set of hosts who engage in update activity.
81
82 Members:
83 table: Table of information on hosts.
84 """
85
86 def __init__(self):
87 # A dictionary of host information. Keys are normally IP addresses.
88 self.table = {}
89
90 def __repr__(self):
91 return '%s' % self.table
92
93 def GetInitHostInfo(self, host_id):
94 """Return a host's info object, or create a new one if none exists."""
95 return self.table.setdefault(host_id, HostInfo())
96
97 def GetHostInfo(self, host_id):
98 """Return an info object for given host, if such exists."""
Chris Sosa1885d032012-11-29 17:07:27 -080099 return self.table.get(host_id)
Gilad Arnold286a0062012-01-12 13:47:02 -0800100
101
rtc@google.com64244662009-11-12 00:52:08 +0000102class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700103 """Class that contains functionality that handles Chrome OS update pings.
104
105 Members:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700106 serve_only: serve only pre-built updates. static_dir must contain
107 update.gz and stateful.tgz.
108 factory_config: path to the factory config file if handling factory
109 requests.
110 use_test_image: use chromiumos_test_image.bin rather than the standard.
111 urlbase: base URL, other than devserver, for update images.
112 forced_image: path to an image to use for all updates.
113 payload_path: path to pre-generated payload to serve.
114 src_image: if specified, creates a delta payload from this image.
115 proxy_port: port of local proxy to tell client to connect to you
116 through.
117 vm: set for VM images (doesn't patch kernel)
118 board: board for the image. Needed for pre-generating of updates.
119 copy_to_static_root: copies images generated from the cache to ~/static.
120 private_key: path to private key in PEM format.
Gilad Arnold8318eac2012-10-04 12:52:23 -0700121 critical_update: whether provisioned payload is critical.
122 remote_payload: whether provisioned payload is remotely staged.
123 max_updates: maximum number of updates we'll try to provision.
124 host_log: record full history of host update events.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700125 """
rtc@google.comded22402009-10-26 22:36:21 +0000126
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700127 _PAYLOAD_URL_PREFIX = '/static/'
128 _FILEINFO_URL_PREFIX = '/api/fileinfo/'
129
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700130 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 14:15:23 -0700131 factory_config_path=None,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700132 forced_image=None, payload_path=None,
133 proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800134 copy_to_static_root=True, private_key=None,
Chris Sosa52148582012-11-15 15:35:58 -0800135 critical_update=False, remote_payload=False, max_updates= -1,
Gilad Arnold8318eac2012-10-04 12:52:23 -0700136 host_log=False,
Chris Sosae67b78f2010-11-04 17:33:16 -0700137 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700138 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700139 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -0700140 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700141 self.use_test_image = test_image
Jay Srinivasanac69d262012-10-30 19:05:53 -0700142 self.hostname = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700143 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700144 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700145 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700146 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700147
Chris Sosa0356d3b2010-09-16 15:46:22 -0700148 self.forced_image = forced_image
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700149 self.payload_path = payload_path
Chris Sosa62f720b2010-10-26 21:39:48 -0700150 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800151 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-28 23:42:37 -0700152 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -0700153 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800154 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800155 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800156 self.critical_update = critical_update
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700157 self.remote_payload = remote_payload
Jay Srinivasanac69d262012-10-30 19:05:53 -0700158 self.max_updates = max_updates
Gilad Arnold8318eac2012-10-04 12:52:23 -0700159 self.host_log = host_log
Don Garrettfff4c322010-11-19 13:37:12 -0800160
Chris Sosa417e55d2011-01-25 16:40:48 -0800161 # Path to pre-generated file.
162 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700163
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700164 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800165 # information about a given host. A host is identified by its IP address.
166 # The info stored for each host includes a complete log of events for this
167 # host, as well as a dictionary of current attributes derived from events.
168 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700169
Chris Sosa0356d3b2010-09-16 15:46:22 -0700170 def _GetDefaultBoardID(self):
171 """Returns the default board id stored in .default_board."""
172 board_file = '%s/.default_board' % (self.scripts_dir)
173 try:
174 return open(board_file).read()
175 except IOError:
176 return 'x86-generic'
177
178 def _GetLatestImageDir(self, board_id):
179 """Returns the latest image dir based on shell script."""
180 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
181 return os.popen(cmd).read().strip()
182
Chris Sosa52148582012-11-15 15:35:58 -0800183 @staticmethod
184 def _GetVersionFromDir(image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700185 """Returns the version of the image based on the name of the directory."""
186 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700187 parts = latest_version.split('-')
188 if len(parts) == 2:
189 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
190 # TODO(derat): Remove the code for old-style versions after 20120101.
191 return parts[0]
192 else:
193 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
194 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700195
Chris Sosa52148582012-11-15 15:35:58 -0800196 @staticmethod
197 def _CanUpdate(client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800198 """Returns true if the latest_version is greater than the client_version.
199 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700200 _Log('client version %s latest version %s'
201 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700202
203 client_tokens = client_version.replace('_', '').split('.')
204 # If the client has an old four-token version like "0.16.892.0", drop the
205 # first two tokens -- we use versions like "892.0.0" now.
206 # TODO(derat): Remove the code for old-style versions after 20120101.
207 if len(client_tokens) == 4:
208 client_tokens = client_tokens[2:]
209
210 latest_tokens = latest_version.replace('_', '').split('.')
211 if len(latest_tokens) == 4:
212 latest_tokens = latest_tokens[2:]
213
214 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700215 if int(latest_tokens[i]) == int(client_tokens[i]):
216 continue
217 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700218
219 # Favor four-token new-style versions on the server over old-style versions
220 # on the client if everything else matches.
221 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700222
Chris Sosa0356d3b2010-09-16 15:46:22 -0700223 def _GetImageName(self):
224 """Returns the name of the image that should be used."""
225 if self.use_test_image:
226 image_name = 'chromiumos_test_image.bin'
227 else:
228 image_name = 'chromiumos_image.bin'
229 return image_name
230
Chris Sosa52148582012-11-15 15:35:58 -0800231 @staticmethod
232 def _IsDeltaFormatFile(filename):
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700233 try:
234 file_handle = open(filename, 'r')
235 delta_magic = 'CrAU'
236 magic = file_handle.read(len(delta_magic))
237 return magic == delta_magic
Jay Srinivasanac69d262012-10-30 19:05:53 -0700238 except IOError:
239 # For unit tests, we may not have real files, so it's ok to
240 # ignore these IOErrors. In any case, this value is not being
241 # used in update_engine at all as of now.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700242 return False
243
Don Garrettf90edf02010-11-16 17:36:14 -0800244 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700245 """Generates an update gz given a full path to an image.
246
247 Args:
248 image_path: Full path to image.
249 Returns:
250 Path to created update_payload or None on error.
251 """
Don Garrettfff4c322010-11-19 13:37:12 -0800252 update_path = os.path.join(output_dir, UPDATE_FILE)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700253 _Log('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700254
Chris Sosa0f1ec842011-02-14 16:33:22 -0800255 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700256 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-14 16:33:22 -0800257 '--image="%s"' % image_path,
258 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800259 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700260
Chris Sosa52148582012-11-15 15:35:58 -0800261 if src_image:
262 update_command.append('--src_image="%s"' % src_image)
263
264 if not self.vm:
265 update_command.append('--patch_kernel')
266
267 if self.private_key:
268 update_command.append('--private_key="%s"' % self.private_key)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800269
270 update_string = ' '.join(update_command)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700271 _Log('Running ' + update_string)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800272 if os.system(update_string) != 0:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700273 _Log('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700274 return None
275
Don Garrettfff4c322010-11-19 13:37:12 -0800276 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700277
Chris Sosa52148582012-11-15 15:35:58 -0800278 @staticmethod
279 def GenerateStatefulFile(image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800280 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700281
282 Args:
283 image_path: Full path to image.
284 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800285 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800286 Raises:
287 A subprocess exception if the update generator fails to generate a
288 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700289 """
Chris Sosa908fd6f2010-11-10 17:31:18 -0800290 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700291 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-10 17:31:18 -0800292 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800293 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800294 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800295 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700296
Don Garrettf90edf02010-11-16 17:36:14 -0800297 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
298 """Find directory to store a cached update.
299
Gilad Arnold55a2a372012-10-02 09:46:32 -0700300 Given one, or two images for an update, this finds which cache directory
301 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-16 17:36:14 -0800302
Gilad Arnold55a2a372012-10-02 09:46:32 -0700303 Returns:
304 A directory path for storing a cached update, of the following form:
305 Non-delta updates:
306 CACHE_DIR/<dest_hash>
307 Delta updates:
308 CACHE_DIR/<src_hash>_<dest_hash>
309 Signed updates (self.private_key):
310 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-07 19:32:50 -0700311 """
Gilad Arnold55a2a372012-10-02 09:46:32 -0700312 update_dir = ''
Chris Sosa744e1472011-09-07 19:32:50 -0700313 if src_image:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700314 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-16 17:36:14 -0800315
Gilad Arnold55a2a372012-10-02 09:46:32 -0700316 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-07 19:32:50 -0700317 if self.private_key:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700318 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-07 19:32:50 -0700319
Chris Sosa9fba7562012-01-31 10:15:47 -0800320 if not self.vm:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700321 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 10:15:47 -0800322
Gilad Arnold55a2a372012-10-02 09:46:32 -0700323 return os.path.join(CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800324
Don Garrettfff4c322010-11-19 13:37:12 -0800325 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800326 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700327
Chris Sosade91f672010-11-16 10:05:44 -0800328 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800329 src_image: image we are updating from (Null/empty for non-delta)
330 image_path: full path to the image.
331 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800332 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800333 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800334 """
Don Garrettf90edf02010-11-16 17:36:14 -0800335 update_file = None
336 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700337
Don Garrettf90edf02010-11-16 17:36:14 -0800338 # Actually do the generation
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700339 _Log('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800340 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800341 image_path,
342 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000343
Don Garrettf90edf02010-11-16 17:36:14 -0800344 if update_file:
345 stateful_update_file = self.GenerateStatefulFile(image_path,
346 output_dir)
347
348 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800349 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800350 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700351 _Log('Failed to generate update.')
Chris Sosa417e55d2011-01-25 16:40:48 -0800352 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800353
354 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
355 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000356
Chris Sosa0356d3b2010-09-16 15:46:22 -0700357 Args:
358 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700359 static_image_dir: the directory to move images to after generating.
360 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800361 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800362 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700363 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700364 _Log('Generating update for src %s image %s' % (self.src_image, image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700365
Chris Sosa417e55d2011-01-25 16:40:48 -0800366 # If it was pregenerated_path, don't regenerate
367 if self.pregenerated_path:
368 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800369
Don Garrettf90edf02010-11-16 17:36:14 -0800370 # Which sub_dir of static_image_dir should hold our cached update image
371 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700372 _Log('Caching in sub_dir "%s"' % cache_sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800373
Chris Sosa417e55d2011-01-25 16:40:48 -0800374 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
375
Don Garrettf90edf02010-11-16 17:36:14 -0800376 # The cached payloads exist in a cache dir
377 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800378 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800379 cache_stateful_payload = os.path.join(static_image_dir,
380 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800381 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800382
Chris Sosa417e55d2011-01-25 16:40:48 -0800383 # Check to see if this cache directory is valid.
384 if not os.path.exists(cache_update_payload) or not os.path.exists(
385 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800386 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800387 # Clean up stale state.
388 os.system('rm -rf "%s"' % full_cache_dir)
389 os.makedirs(full_cache_dir)
390 return_path = self.GenerateUpdateImage(image_path,
391 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800392
Chris Sosa417e55d2011-01-25 16:40:48 -0800393 # Clean up cache dir since it's not valid.
394 if not return_path:
395 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800396 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800397
398 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800399
Chris Sosa08d55a22011-01-19 16:08:02 -0800400 # Generation complete, copy if requested.
401 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800402 # The final results exist directly in static
403 update_payload = os.path.join(static_image_dir,
404 UPDATE_FILE)
405 stateful_payload = os.path.join(static_image_dir,
406 STATEFUL_FILE)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700407 common_util.CopyFile(cache_update_payload, update_payload)
408 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800409 return UPDATE_FILE
410 else:
411 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700412
413 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800414 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700415 """Generates an update using the latest image that has been built.
416
417 This will only generate an update if the newest update is newer than that
418 on the client or client_version is 'ForcedUpdate'.
419
420 Args:
421 board_id: Name of the board.
422 client_version: Current version of the client or 'ForcedUpdate'
423 static_image_dir: the directory to move images to after generating.
424 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800425 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700426 """
427 latest_image_dir = self._GetLatestImageDir(board_id)
428 latest_version = self._GetVersionFromDir(latest_image_dir)
429 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
430
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700431 _Log('Preparing to generate update from latest built image %s.' %
432 latest_image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700433
434 # Check to see whether or not we should update.
435 if client_version != 'ForcedUpdate' and not self._CanUpdate(
436 client_version, latest_version):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700437 _Log('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800438 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700439
Don Garrettf90edf02010-11-16 17:36:14 -0800440 return self.GenerateUpdateImageWithCache(latest_image_path,
441 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700442
Andrew de los Reyes52620802010-04-12 13:40:07 -0700443 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
444 """Imports a factory-floor server configuration file. The file should
445 be in this format:
446 config = [
447 {
448 'qual_ids': set([1, 2, 3, "x86-generic"]),
449 'factory_image': 'generic-factory.gz',
450 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
451 'release_image': 'generic-release.gz',
452 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
453 'oempartitionimg_image': 'generic-oem.gz',
454 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700455 'efipartitionimg_image': 'generic-efi.gz',
456 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700457 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800458 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800459 'firmware_image': 'generic-firmware.gz',
460 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700461 },
462 {
463 'qual_ids': set([6]),
464 'factory_image': '6-factory.gz',
465 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
466 'release_image': '6-release.gz',
467 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
468 'oempartitionimg_image': '6-oem.gz',
469 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700470 'efipartitionimg_image': '6-efi.gz',
471 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700472 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800473 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800474 'firmware_image': '6-firmware.gz',
475 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700476 },
477 ]
478 The server will look for the files by name in the static files
479 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700480
Andrew de los Reyes52620802010-04-12 13:40:07 -0700481 If validate_checksums is True, validates checksums and exits. If
482 a checksum mismatch is found, it's printed to the screen.
483 """
484 f = open(filename, 'r')
485 output = {}
486 exec(f.read(), output)
487 self.factory_config = output['config']
488 success = True
489 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800490 for key in stanza.copy().iterkeys():
491 suffix = '_image'
492 if key.endswith(suffix):
493 kind = key[:-len(suffix)]
Gilad Arnold55a2a372012-10-02 09:46:32 -0700494 stanza[kind + '_size'] = common_util.GetFileSize(os.path.join(
Chris Sosa0356d3b2010-09-16 15:46:22 -0700495 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800496 if validate_checksums:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700497 factory_checksum = common_util.GetFileSha1(
498 os.path.join(self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800499 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700500 print ('Error: checksum mismatch for %s. Expected "%s" but file '
501 'has checksum "%s".' % (stanza[kind + '_image'],
502 stanza[kind + '_checksum'],
503 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800504 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700505
Andrew de los Reyes52620802010-04-12 13:40:07 -0700506 if validate_checksums:
507 if success is False:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700508 raise AutoupdateError('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700509
Andrew de los Reyes52620802010-04-12 13:40:07 -0700510 print 'Config file looks good.'
511
512 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700513 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700514 for stanza in self.factory_config:
515 if board_id not in stanza['qual_ids']:
516 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700517 if kind + '_image' not in stanza:
518 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700519 return (stanza[kind + '_image'],
520 stanza[kind + '_checksum'],
521 stanza[kind + '_size'])
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700522 return None, None, None
rtc@google.comded22402009-10-26 22:36:21 +0000523
Jay Srinivasanac69d262012-10-30 19:05:53 -0700524 def HandleFactoryRequest(self, board_id, channel, protocol):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700525 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
526 if filename is None:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700527 _Log('unable to find image for board %s' % board_id)
Chris Sosa52148582012-11-15 15:35:58 -0800528 return autoupdate_lib.GetNoUpdateResponse(protocol)
Chris Sosa05f95162010-10-14 18:01:52 -0700529 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700530 is_delta_format = self._IsDeltaFormatFile(filename)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700531 _Log('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700532 # Factory install is using memento updater which is using the sha-1 hash so
533 # setting sha-256 to an empty string.
Chris Sosa52148582012-11-15 15:35:58 -0800534 return autoupdate_lib.GetUpdateResponse(checksum, '', size, url,
535 is_delta_format, protocol,
536 self.critical_update)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700537
Chris Sosa151643e2010-10-28 14:40:57 -0700538 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
539 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800540 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800541
Don Garrettf90edf02010-11-16 17:36:14 -0800542 Returns:
543 file name relative to static_image_dir on success.
544 """
Dale Curtis723ec472010-11-30 14:06:47 -0800545 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
546 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
547
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700548 if self.payload_path:
Don Garrett0c880e22010-11-17 18:13:37 -0800549 # If the forced payload is not already in our static_image_dir,
550 # copy it there.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700551 src_path = os.path.abspath(self.payload_path)
Don Garrettee25e552010-11-23 12:09:35 -0800552 src_stateful = os.path.join(os.path.dirname(src_path),
553 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800554
555 # Only copy the files if the source directory is different from dest.
556 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700557 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 12:09:35 -0800558
559 # The stateful payload is optional.
560 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700561 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 12:09:35 -0800562 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700563 _Log('WARN: %s not found. Expected for dev and test builds.' %
564 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800565 if os.path.exists(dest_stateful):
566 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800567
Don Garrettfff4c322010-11-19 13:37:12 -0800568 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800569 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800570 return self.GenerateUpdateImageWithCache(
571 self.forced_image,
572 static_image_dir=static_image_dir)
573 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800574 # Warn if update or stateful files can't be found.
575 if not os.path.exists(dest_path):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700576 _Log('WARN: %s not found. Expected for dev and test builds.' %
577 UPDATE_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800578
579 if not os.path.exists(dest_stateful):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700580 _Log('WARN: %s not found. Expected for dev and test builds.' %
581 STATEFUL_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800582
583 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800584 else:
585 if board_id:
586 return self.GenerateLatestUpdateImage(board_id,
587 client_version,
588 static_image_dir)
589
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700590 _Log('Failed to genereate update. '
591 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800592 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700593
594 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800595 """Pre-generates an update and prints out the relative path it.
596
597 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800598 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700599 # Does not work with factory config.
600 assert(not self.factory_config)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700601 _Log('Pre-generating the update payload.')
Chris Sosa2c048f12010-10-27 16:05:27 -0700602 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800603 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
604 self.board, '0.0.0.0', self.static_dir)
605 if pregenerated_update:
606 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
607
608 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700609
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700610 def _GetRemotePayloadAttrs(self, url):
611 """Returns hashes, size and delta flag of a remote update payload.
612
613 Obtain attributes of a payload file available on a remote devserver. This
614 is based on the assumption that the payload URL uses the /static prefix. We
615 need to make sure that both clients (requests) and remote devserver
616 (provisioning) preserve this invariant.
617
618 Args:
619 url: URL of statically staged remote file (http://host:port/static/...)
620 Returns:
621 A tuple containing the SHA1, SHA256, file size and whether or not it's a
622 delta payload (Boolean).
623 """
624 if self._PAYLOAD_URL_PREFIX not in url:
625 raise AutoupdateError(
626 'Payload URL does not have the expected prefix (%s)' %
627 self._PAYLOAD_URL_PREFIX)
628 fileinfo_url = url.replace(self._PAYLOAD_URL_PREFIX,
629 self._FILEINFO_URL_PREFIX)
630 _Log('retrieving file info for remote payload via %s' % fileinfo_url)
631 try:
632 conn = urllib2.urlopen(fileinfo_url)
633 file_attr_dict = json.loads(conn.read())
634 sha1 = file_attr_dict['sha1']
635 sha256 = file_attr_dict['sha256']
636 size = file_attr_dict['size']
637 except Exception, e:
638 _Log('failed to obtain remote payload info: %s' % str(e))
639 raise
640 is_delta_format = ('_mton' in url) or ('_nton' in url)
641
642 return sha1, sha256, size, is_delta_format
643
644 def _GetLocalPayloadAttrs(self, static_image_dir, payload_path):
645 """Returns hashes, size and delta flag of a local update payload.
646
647 Args:
648 static_image_dir: directory where static files are being staged
649 payload_path: path to the payload file inside the static directory
650 Returns:
651 A tuple containing the SHA1, SHA256, file size and whether or not it's a
652 delta payload (Boolean).
653 """
654 filename = os.path.join(static_image_dir, payload_path)
655 sha1 = common_util.GetFileSha1(filename)
656 sha256 = common_util.GetFileSha256(filename)
657 size = common_util.GetFileSize(filename)
658 is_delta_format = self._IsDeltaFormatFile(filename)
659 return sha1, sha256, size, is_delta_format
660
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700661 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700662 """Handles an update ping from an update client.
663
664 Args:
665 data: xml blob from client.
666 label: optional label for the update.
667 Returns:
668 Update payload message for client.
669 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700670 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 16:26:10 -0700671 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
672 # be in X-Forwarded-Host.
673 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
674 if x_forwarded_host:
675 self.hostname = 'http://' + x_forwarded_host
676 else:
677 self.hostname = cherrypy.request.base
678
Chris Sosa9841e1c2010-10-14 10:51:45 -0700679 if self.urlbase:
680 static_urlbase = self.urlbase
681 elif self.serve_only:
682 static_urlbase = '%s/static/archive' % self.hostname
683 else:
684 static_urlbase = '%s/static' % self.hostname
685
Don Garrett0ad09372010-12-06 16:20:30 -0800686 # If we have a proxy port, adjust the URL we instruct the client to
687 # use to go through the proxy.
688 if self.proxy_port:
689 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
690
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700691 _Log('Using static url base %s' % static_urlbase)
692 _Log('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700693
Chris Sosa52148582012-11-15 15:35:58 -0800694 protocol, app, event, update_check = autoupdate_lib.ParseUpdateRequest(data)
Jay Srinivasanac69d262012-10-30 19:05:53 -0700695 _Log('Client is using protocol version: %s' % protocol)
Jay Srinivasanac69d262012-10-30 19:05:53 -0700696
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700697 # Determine request IP, strip any IPv6 data for simplicity.
698 client_ip = cherrypy.request.remote.ip.split(':')[-1]
699
Gilad Arnold286a0062012-01-12 13:47:02 -0800700 # Obtain (or init) info object for this client.
701 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
702
703 # Initialize an empty dictionary for event attributes.
704 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700705
706 # Store event details in the host info dictionary for API usage.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700707 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800708 event_result = int(event[0].getAttribute('eventresult'))
709 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700710 client_previous_version = (event[0].getAttribute('previousversion')
711 if event[0].hasAttribute('previousversion')
712 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800713 # Store attributes to legacy host info structure
714 curr_host_info.attrs['last_event_status'] = event_result
715 curr_host_info.attrs['last_event_type'] = event_type
716 # Add attributes to log message
717 log_message['event_result'] = event_result
718 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700719 if client_previous_version is not None:
720 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800721
722 # Get information about the requester.
Chris Sosa52148582012-11-15 15:35:58 -0800723 if app:
724 client_version = app.getAttribute('version')
725 channel = app.getAttribute('track')
726 board_id = (app.hasAttribute('board') and app.getAttribute('board')
727 or self._GetDefaultBoardID())
Gilad Arnold286a0062012-01-12 13:47:02 -0800728 # Add attributes to log message
729 log_message['version'] = client_version
730 log_message['track'] = channel
731 log_message['board'] = board_id
732
Gilad Arnold8318eac2012-10-04 12:52:23 -0700733 # Log host event, if so instructed.
734 if self.host_log:
735 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700736
Chris Sosa0356d3b2010-09-16 15:46:22 -0700737 # We only generate update payloads for updatecheck requests.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700738 if not update_check:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700739 _Log('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700740 # TODO(sosa): Generate correct non-updatecheck payload to better test
741 # update clients.
Chris Sosa52148582012-11-15 15:35:58 -0800742 return autoupdate_lib.GetNoUpdateResponse(protocol)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700743
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700744 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800745 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700746
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700747 # If maximum number of updates already requested, refuse.
748 if self.max_updates > 0:
749 self.max_updates -= 1
750 elif self.max_updates == 0:
Chris Sosa52148582012-11-15 15:35:58 -0800751 return autoupdate_lib.GetNoUpdateResponse(protocol)
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700752
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700753 # Check if an update has been forced for this client.
Chris Sosa1885d032012-11-29 17:07:27 -0800754 forced_update = curr_host_info.attrs.pop('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700755 if forced_update:
756 label = forced_update
757
Chris Sosa0356d3b2010-09-16 15:46:22 -0700758 # Separate logic as Factory requests have static url's that override
759 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700760 if self.factory_config:
Jay Srinivasanac69d262012-10-30 19:05:53 -0700761 return self.HandleFactoryRequest(board_id, channel, protocol)
Nick Sanders723f3262010-09-16 05:18:41 -0700762 else:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700763 url = ''
764 # Are we provisioning a remote or local payload?
765 if self.remote_payload:
766 # If no explicit label was provided, use the value of --payload.
767 if not label and self.payload_path:
768 label = self.payload_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700769
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700770 # Form the URL of the update payload. This assumes that the payload
771 # file name is a devserver constant (which currently is the case).
772 url = '/'.join(filter(None, [static_urlbase, label, UPDATE_FILE]))
Chris Sosa5d342a22010-09-28 16:54:41 -0700773
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700774 # Get remote payload attributes.
775 sha1, sha256, file_size, is_delta_format = \
776 self._GetRemotePayloadAttrs(url)
777 else:
778 # Generate payload.
779 static_image_dir = os.path.join(*filter(None, [self.static_dir, label]))
780 payload_path = self.GenerateUpdatePayloadForNonFactory(
781 board_id, client_version, static_image_dir)
782 # If properly generated, obtain the payload URL and attributes.
783 if payload_path:
784 url = '/'.join(filter(None, [static_urlbase, label, payload_path]))
785 sha1, sha256, file_size, is_delta_format = \
786 self._GetLocalPayloadAttrs(static_image_dir, payload_path)
787
788 # If we end up with an actual payload path, generate a response.
789 if url:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700790 _Log('Responding to client to use url %s to get image.' % url)
Chris Sosa52148582012-11-15 15:35:58 -0800791 return autoupdate_lib.GetUpdateResponse(
792 sha1, sha256, file_size, url, is_delta_format, protocol,
793 self.critical_update)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700794 else:
Chris Sosa52148582012-11-15 15:35:58 -0800795 return autoupdate_lib.GetNoUpdateResponse(protocol)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700796
797 def HandleHostInfoPing(self, ip):
798 """Returns host info dictionary for the given IP in JSON format."""
799 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800800 if ip in self.host_infos.table:
801 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
802
803 def HandleHostLogPing(self, ip):
804 """Returns a complete log of events for host in JSON format."""
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700805 # If all events requested, return a dictionary of logs keyed by IP address.
Gilad Arnold286a0062012-01-12 13:47:02 -0800806 if ip == 'all':
807 return json.dumps(
808 dict([(key, self.host_infos.table[key].log)
809 for key in self.host_infos.table]))
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700810
811 # Otherwise we're looking for a specific IP address, so find its log.
Gilad Arnold286a0062012-01-12 13:47:02 -0800812 if ip in self.host_infos.table:
813 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700814
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700815 # If no events were logged for this IP, return an empty log.
816 return json.dumps([])
817
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700818 def HandleSetUpdatePing(self, ip, label):
819 """Sets forced_update_label for a given host."""
820 assert ip, 'No ip provided.'
821 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800822 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label