blob: 9b42eac68dfa05c7d095ac8b6b9a16d5cfd65db1 [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
78 def SetAttr(self, attr, value):
79 """Set an attribute value."""
80 self.attrs[attr] = value
81
82 def GetAttr(self, attr):
83 """Returns the value of an attribute."""
84 if attr in self.attrs:
85 return self.attrs[attr]
86
87 def PopAttr(self, attr, default):
88 """Returns and deletes a particular attribute."""
89 return self.attrs.pop(attr, default)
90
91
92class HostInfoTable:
93 """Records information about a set of hosts who engage in update activity.
94
95 Members:
96 table: Table of information on hosts.
97 """
98
99 def __init__(self):
100 # A dictionary of host information. Keys are normally IP addresses.
101 self.table = {}
102
103 def __repr__(self):
104 return '%s' % self.table
105
106 def GetInitHostInfo(self, host_id):
107 """Return a host's info object, or create a new one if none exists."""
108 return self.table.setdefault(host_id, HostInfo())
109
110 def GetHostInfo(self, host_id):
111 """Return an info object for given host, if such exists."""
112 if host_id in self.table:
113 return self.table[host_id]
114
115
rtc@google.com64244662009-11-12 00:52:08 +0000116class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700117 """Class that contains functionality that handles Chrome OS update pings.
118
119 Members:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700120 serve_only: serve only pre-built updates. static_dir must contain
121 update.gz and stateful.tgz.
122 factory_config: path to the factory config file if handling factory
123 requests.
124 use_test_image: use chromiumos_test_image.bin rather than the standard.
125 urlbase: base URL, other than devserver, for update images.
126 forced_image: path to an image to use for all updates.
127 payload_path: path to pre-generated payload to serve.
128 src_image: if specified, creates a delta payload from this image.
129 proxy_port: port of local proxy to tell client to connect to you
130 through.
131 vm: set for VM images (doesn't patch kernel)
132 board: board for the image. Needed for pre-generating of updates.
133 copy_to_static_root: copies images generated from the cache to ~/static.
134 private_key: path to private key in PEM format.
Gilad Arnold8318eac2012-10-04 12:52:23 -0700135 critical_update: whether provisioned payload is critical.
136 remote_payload: whether provisioned payload is remotely staged.
137 max_updates: maximum number of updates we'll try to provision.
138 host_log: record full history of host update events.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700139 """
rtc@google.comded22402009-10-26 22:36:21 +0000140
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700141 _PAYLOAD_URL_PREFIX = '/static/'
142 _FILEINFO_URL_PREFIX = '/api/fileinfo/'
143
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700144 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 14:15:23 -0700145 factory_config_path=None,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700146 forced_image=None, payload_path=None,
147 proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800148 copy_to_static_root=True, private_key=None,
Chris Sosa52148582012-11-15 15:35:58 -0800149 critical_update=False, remote_payload=False, max_updates= -1,
Gilad Arnold8318eac2012-10-04 12:52:23 -0700150 host_log=False,
Chris Sosae67b78f2010-11-04 17:33:16 -0700151 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700152 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700153 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -0700154 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700155 self.use_test_image = test_image
Jay Srinivasanac69d262012-10-30 19:05:53 -0700156 self.hostname = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700157 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700158 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700159 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700160 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700161
Chris Sosa0356d3b2010-09-16 15:46:22 -0700162 self.forced_image = forced_image
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700163 self.payload_path = payload_path
Chris Sosa62f720b2010-10-26 21:39:48 -0700164 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800165 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-28 23:42:37 -0700166 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -0700167 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800168 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800169 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800170 self.critical_update = critical_update
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700171 self.remote_payload = remote_payload
Jay Srinivasanac69d262012-10-30 19:05:53 -0700172 self.max_updates = max_updates
Gilad Arnold8318eac2012-10-04 12:52:23 -0700173 self.host_log = host_log
Don Garrettfff4c322010-11-19 13:37:12 -0800174
Chris Sosa417e55d2011-01-25 16:40:48 -0800175 # Path to pre-generated file.
176 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700177
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700178 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800179 # information about a given host. A host is identified by its IP address.
180 # The info stored for each host includes a complete log of events for this
181 # host, as well as a dictionary of current attributes derived from events.
182 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700183
Chris Sosa0356d3b2010-09-16 15:46:22 -0700184 def _GetDefaultBoardID(self):
185 """Returns the default board id stored in .default_board."""
186 board_file = '%s/.default_board' % (self.scripts_dir)
187 try:
188 return open(board_file).read()
189 except IOError:
190 return 'x86-generic'
191
192 def _GetLatestImageDir(self, board_id):
193 """Returns the latest image dir based on shell script."""
194 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
195 return os.popen(cmd).read().strip()
196
Chris Sosa52148582012-11-15 15:35:58 -0800197 @staticmethod
198 def _GetVersionFromDir(image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700199 """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 08:52:52 -0700201 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 15:46:22 -0700209
Chris Sosa52148582012-11-15 15:35:58 -0800210 @staticmethod
211 def _CanUpdate(client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800212 """Returns true if the latest_version is greater than the client_version.
213 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700214 _Log('client version %s latest version %s'
215 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700216
217 client_tokens = client_version.replace('_', '').split('.')
218 # If the client has an old four-token version like "0.16.892.0", drop the
219 # first two tokens -- we use versions like "892.0.0" now.
220 # TODO(derat): Remove the code for old-style versions after 20120101.
221 if len(client_tokens) == 4:
222 client_tokens = client_tokens[2:]
223
224 latest_tokens = latest_version.replace('_', '').split('.')
225 if len(latest_tokens) == 4:
226 latest_tokens = latest_tokens[2:]
227
228 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700229 if int(latest_tokens[i]) == int(client_tokens[i]):
230 continue
231 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700232
233 # Favor four-token new-style versions on the server over old-style versions
234 # on the client if everything else matches.
235 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700236
Chris Sosa0356d3b2010-09-16 15:46:22 -0700237 def _UnpackZip(self, image_dir):
238 """Unpacks an image.zip into a given directory."""
239 image = os.path.join(image_dir, self._GetImageName())
240 if os.path.exists(image):
241 return True
242 else:
243 # -n, never clobber an existing file, in case we get invoked
244 # simultaneously by multiple request handlers. This means that
245 # we're assuming each image.zip file lives in a versioned
246 # directory (a la Buildbot).
247 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
248
249 def _GetImageName(self):
250 """Returns the name of the image that should be used."""
251 if self.use_test_image:
252 image_name = 'chromiumos_test_image.bin'
253 else:
254 image_name = 'chromiumos_image.bin'
255 return image_name
256
Chris Sosa52148582012-11-15 15:35:58 -0800257 @staticmethod
258 def _IsDeltaFormatFile(filename):
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700259 try:
260 file_handle = open(filename, 'r')
261 delta_magic = 'CrAU'
262 magic = file_handle.read(len(delta_magic))
263 return magic == delta_magic
Jay Srinivasanac69d262012-10-30 19:05:53 -0700264 except IOError:
265 # For unit tests, we may not have real files, so it's ok to
266 # ignore these IOErrors. In any case, this value is not being
267 # used in update_engine at all as of now.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700268 return False
269
Don Garrettf90edf02010-11-16 17:36:14 -0800270 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700271 """Generates an update gz given a full path to an image.
272
273 Args:
274 image_path: Full path to image.
275 Returns:
276 Path to created update_payload or None on error.
277 """
Don Garrettfff4c322010-11-19 13:37:12 -0800278 update_path = os.path.join(output_dir, UPDATE_FILE)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700279 _Log('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700280
Chris Sosa0f1ec842011-02-14 16:33:22 -0800281 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700282 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-14 16:33:22 -0800283 '--image="%s"' % image_path,
284 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800285 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700286
Chris Sosa52148582012-11-15 15:35:58 -0800287 if src_image:
288 update_command.append('--src_image="%s"' % src_image)
289
290 if not self.vm:
291 update_command.append('--patch_kernel')
292
293 if self.private_key:
294 update_command.append('--private_key="%s"' % self.private_key)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800295
296 update_string = ' '.join(update_command)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700297 _Log('Running ' + update_string)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800298 if os.system(update_string) != 0:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700299 _Log('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700300 return None
301
Don Garrettfff4c322010-11-19 13:37:12 -0800302 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700303
Chris Sosa52148582012-11-15 15:35:58 -0800304 @staticmethod
305 def GenerateStatefulFile(image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800306 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700307
308 Args:
309 image_path: Full path to image.
310 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800311 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800312 Raises:
313 A subprocess exception if the update generator fails to generate a
314 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700315 """
Chris Sosa908fd6f2010-11-10 17:31:18 -0800316 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700317 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-10 17:31:18 -0800318 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800319 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800320 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800321 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700322
Don Garrettf90edf02010-11-16 17:36:14 -0800323 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
324 """Find directory to store a cached update.
325
Gilad Arnold55a2a372012-10-02 09:46:32 -0700326 Given one, or two images for an update, this finds which cache directory
327 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-16 17:36:14 -0800328
Gilad Arnold55a2a372012-10-02 09:46:32 -0700329 Returns:
330 A directory path for storing a cached update, of the following form:
331 Non-delta updates:
332 CACHE_DIR/<dest_hash>
333 Delta updates:
334 CACHE_DIR/<src_hash>_<dest_hash>
335 Signed updates (self.private_key):
336 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-07 19:32:50 -0700337 """
Gilad Arnold55a2a372012-10-02 09:46:32 -0700338 update_dir = ''
Chris Sosa744e1472011-09-07 19:32:50 -0700339 if src_image:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700340 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-16 17:36:14 -0800341
Gilad Arnold55a2a372012-10-02 09:46:32 -0700342 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-07 19:32:50 -0700343 if self.private_key:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700344 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-07 19:32:50 -0700345
Chris Sosa9fba7562012-01-31 10:15:47 -0800346 if not self.vm:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700347 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 10:15:47 -0800348
Gilad Arnold55a2a372012-10-02 09:46:32 -0700349 return os.path.join(CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800350
Don Garrettfff4c322010-11-19 13:37:12 -0800351 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800352 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700353
Chris Sosade91f672010-11-16 10:05:44 -0800354 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800355 src_image: image we are updating from (Null/empty for non-delta)
356 image_path: full path to the image.
357 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800358 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800359 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800360 """
Don Garrettf90edf02010-11-16 17:36:14 -0800361 update_file = None
362 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700363
Don Garrettf90edf02010-11-16 17:36:14 -0800364 # Actually do the generation
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700365 _Log('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800366 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800367 image_path,
368 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000369
Don Garrettf90edf02010-11-16 17:36:14 -0800370 if update_file:
371 stateful_update_file = self.GenerateStatefulFile(image_path,
372 output_dir)
373
374 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800375 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800376 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700377 _Log('Failed to generate update.')
Chris Sosa417e55d2011-01-25 16:40:48 -0800378 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800379
380 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
381 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000382
Chris Sosa0356d3b2010-09-16 15:46:22 -0700383 Args:
384 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700385 static_image_dir: the directory to move images to after generating.
386 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800387 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800388 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700389 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700390 _Log('Generating update for src %s image %s' % (self.src_image, image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700391
Chris Sosa417e55d2011-01-25 16:40:48 -0800392 # If it was pregenerated_path, don't regenerate
393 if self.pregenerated_path:
394 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800395
Don Garrettf90edf02010-11-16 17:36:14 -0800396 # Which sub_dir of static_image_dir should hold our cached update image
397 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700398 _Log('Caching in sub_dir "%s"' % cache_sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800399
Chris Sosa417e55d2011-01-25 16:40:48 -0800400 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
401
Don Garrettf90edf02010-11-16 17:36:14 -0800402 # The cached payloads exist in a cache dir
403 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800404 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800405 cache_stateful_payload = os.path.join(static_image_dir,
406 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800407 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800408
Chris Sosa417e55d2011-01-25 16:40:48 -0800409 # Check to see if this cache directory is valid.
410 if not os.path.exists(cache_update_payload) or not os.path.exists(
411 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800412 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800413 # Clean up stale state.
414 os.system('rm -rf "%s"' % full_cache_dir)
415 os.makedirs(full_cache_dir)
416 return_path = self.GenerateUpdateImage(image_path,
417 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800418
Chris Sosa417e55d2011-01-25 16:40:48 -0800419 # Clean up cache dir since it's not valid.
420 if not return_path:
421 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800422 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800423
424 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800425
Chris Sosa08d55a22011-01-19 16:08:02 -0800426 # Generation complete, copy if requested.
427 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800428 # The final results exist directly in static
429 update_payload = os.path.join(static_image_dir,
430 UPDATE_FILE)
431 stateful_payload = os.path.join(static_image_dir,
432 STATEFUL_FILE)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700433 common_util.CopyFile(cache_update_payload, update_payload)
434 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800435 return UPDATE_FILE
436 else:
437 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700438
439 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800440 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700441 """Generates an update using the latest image that has been built.
442
443 This will only generate an update if the newest update is newer than that
444 on the client or client_version is 'ForcedUpdate'.
445
446 Args:
447 board_id: Name of the board.
448 client_version: Current version of the client or 'ForcedUpdate'
449 static_image_dir: the directory to move images to after generating.
450 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800451 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700452 """
453 latest_image_dir = self._GetLatestImageDir(board_id)
454 latest_version = self._GetVersionFromDir(latest_image_dir)
455 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
456
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700457 _Log('Preparing to generate update from latest built image %s.' %
458 latest_image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700459
460 # Check to see whether or not we should update.
461 if client_version != 'ForcedUpdate' and not self._CanUpdate(
462 client_version, latest_version):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700463 _Log('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800464 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700465
Don Garrettf90edf02010-11-16 17:36:14 -0800466 return self.GenerateUpdateImageWithCache(latest_image_path,
467 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700468
Andrew de los Reyes52620802010-04-12 13:40:07 -0700469 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
470 """Imports a factory-floor server configuration file. The file should
471 be in this format:
472 config = [
473 {
474 'qual_ids': set([1, 2, 3, "x86-generic"]),
475 'factory_image': 'generic-factory.gz',
476 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
477 'release_image': 'generic-release.gz',
478 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
479 'oempartitionimg_image': 'generic-oem.gz',
480 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700481 'efipartitionimg_image': 'generic-efi.gz',
482 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700483 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800484 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800485 'firmware_image': 'generic-firmware.gz',
486 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700487 },
488 {
489 'qual_ids': set([6]),
490 'factory_image': '6-factory.gz',
491 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
492 'release_image': '6-release.gz',
493 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
494 'oempartitionimg_image': '6-oem.gz',
495 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700496 'efipartitionimg_image': '6-efi.gz',
497 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700498 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800499 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800500 'firmware_image': '6-firmware.gz',
501 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700502 },
503 ]
504 The server will look for the files by name in the static files
505 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700506
Andrew de los Reyes52620802010-04-12 13:40:07 -0700507 If validate_checksums is True, validates checksums and exits. If
508 a checksum mismatch is found, it's printed to the screen.
509 """
510 f = open(filename, 'r')
511 output = {}
512 exec(f.read(), output)
513 self.factory_config = output['config']
514 success = True
515 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800516 for key in stanza.copy().iterkeys():
517 suffix = '_image'
518 if key.endswith(suffix):
519 kind = key[:-len(suffix)]
Gilad Arnold55a2a372012-10-02 09:46:32 -0700520 stanza[kind + '_size'] = common_util.GetFileSize(os.path.join(
Chris Sosa0356d3b2010-09-16 15:46:22 -0700521 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800522 if validate_checksums:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700523 factory_checksum = common_util.GetFileSha1(
524 os.path.join(self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800525 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700526 print ('Error: checksum mismatch for %s. Expected "%s" but file '
527 'has checksum "%s".' % (stanza[kind + '_image'],
528 stanza[kind + '_checksum'],
529 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800530 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700531
Andrew de los Reyes52620802010-04-12 13:40:07 -0700532 if validate_checksums:
533 if success is False:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700534 raise AutoupdateError('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700535
Andrew de los Reyes52620802010-04-12 13:40:07 -0700536 print 'Config file looks good.'
537
538 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700539 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700540 for stanza in self.factory_config:
541 if board_id not in stanza['qual_ids']:
542 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700543 if kind + '_image' not in stanza:
544 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700545 return (stanza[kind + '_image'],
546 stanza[kind + '_checksum'],
547 stanza[kind + '_size'])
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700548 return None, None, None
rtc@google.comded22402009-10-26 22:36:21 +0000549
Jay Srinivasanac69d262012-10-30 19:05:53 -0700550 def HandleFactoryRequest(self, board_id, channel, protocol):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700551 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
552 if filename is None:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700553 _Log('unable to find image for board %s' % board_id)
Chris Sosa52148582012-11-15 15:35:58 -0800554 return autoupdate_lib.GetNoUpdateResponse(protocol)
Chris Sosa05f95162010-10-14 18:01:52 -0700555 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700556 is_delta_format = self._IsDeltaFormatFile(filename)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700557 _Log('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700558 # Factory install is using memento updater which is using the sha-1 hash so
559 # setting sha-256 to an empty string.
Chris Sosa52148582012-11-15 15:35:58 -0800560 return autoupdate_lib.GetUpdateResponse(checksum, '', size, url,
561 is_delta_format, protocol,
562 self.critical_update)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700563
Chris Sosa151643e2010-10-28 14:40:57 -0700564 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
565 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800566 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800567
Don Garrettf90edf02010-11-16 17:36:14 -0800568 Returns:
569 file name relative to static_image_dir on success.
570 """
Dale Curtis723ec472010-11-30 14:06:47 -0800571 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
572 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
573
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700574 if self.payload_path:
Don Garrett0c880e22010-11-17 18:13:37 -0800575 # If the forced payload is not already in our static_image_dir,
576 # copy it there.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700577 src_path = os.path.abspath(self.payload_path)
Don Garrettee25e552010-11-23 12:09:35 -0800578 src_stateful = os.path.join(os.path.dirname(src_path),
579 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800580
581 # Only copy the files if the source directory is different from dest.
582 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700583 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 12:09:35 -0800584
585 # The stateful payload is optional.
586 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700587 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 12:09:35 -0800588 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700589 _Log('WARN: %s not found. Expected for dev and test builds.' %
590 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800591 if os.path.exists(dest_stateful):
592 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800593
Don Garrettfff4c322010-11-19 13:37:12 -0800594 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800595 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800596 return self.GenerateUpdateImageWithCache(
597 self.forced_image,
598 static_image_dir=static_image_dir)
599 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800600 # Warn if update or stateful files can't be found.
601 if not os.path.exists(dest_path):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700602 _Log('WARN: %s not found. Expected for dev and test builds.' %
603 UPDATE_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800604
605 if not os.path.exists(dest_stateful):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700606 _Log('WARN: %s not found. Expected for dev and test builds.' %
607 STATEFUL_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800608
609 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800610 else:
611 if board_id:
612 return self.GenerateLatestUpdateImage(board_id,
613 client_version,
614 static_image_dir)
615
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700616 _Log('Failed to genereate update. '
617 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800618 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700619
620 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800621 """Pre-generates an update and prints out the relative path it.
622
623 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800624 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700625 # Does not work with factory config.
626 assert(not self.factory_config)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700627 _Log('Pre-generating the update payload.')
Chris Sosa2c048f12010-10-27 16:05:27 -0700628 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800629 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
630 self.board, '0.0.0.0', self.static_dir)
631 if pregenerated_update:
632 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
633
634 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700635
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700636 def _GetRemotePayloadAttrs(self, url):
637 """Returns hashes, size and delta flag of a remote update payload.
638
639 Obtain attributes of a payload file available on a remote devserver. This
640 is based on the assumption that the payload URL uses the /static prefix. We
641 need to make sure that both clients (requests) and remote devserver
642 (provisioning) preserve this invariant.
643
644 Args:
645 url: URL of statically staged remote file (http://host:port/static/...)
646 Returns:
647 A tuple containing the SHA1, SHA256, file size and whether or not it's a
648 delta payload (Boolean).
649 """
650 if self._PAYLOAD_URL_PREFIX not in url:
651 raise AutoupdateError(
652 'Payload URL does not have the expected prefix (%s)' %
653 self._PAYLOAD_URL_PREFIX)
654 fileinfo_url = url.replace(self._PAYLOAD_URL_PREFIX,
655 self._FILEINFO_URL_PREFIX)
656 _Log('retrieving file info for remote payload via %s' % fileinfo_url)
657 try:
658 conn = urllib2.urlopen(fileinfo_url)
659 file_attr_dict = json.loads(conn.read())
660 sha1 = file_attr_dict['sha1']
661 sha256 = file_attr_dict['sha256']
662 size = file_attr_dict['size']
663 except Exception, e:
664 _Log('failed to obtain remote payload info: %s' % str(e))
665 raise
666 is_delta_format = ('_mton' in url) or ('_nton' in url)
667
668 return sha1, sha256, size, is_delta_format
669
670 def _GetLocalPayloadAttrs(self, static_image_dir, payload_path):
671 """Returns hashes, size and delta flag of a local update payload.
672
673 Args:
674 static_image_dir: directory where static files are being staged
675 payload_path: path to the payload file inside the static directory
676 Returns:
677 A tuple containing the SHA1, SHA256, file size and whether or not it's a
678 delta payload (Boolean).
679 """
680 filename = os.path.join(static_image_dir, payload_path)
681 sha1 = common_util.GetFileSha1(filename)
682 sha256 = common_util.GetFileSha256(filename)
683 size = common_util.GetFileSize(filename)
684 is_delta_format = self._IsDeltaFormatFile(filename)
685 return sha1, sha256, size, is_delta_format
686
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700687 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700688 """Handles an update ping from an update client.
689
690 Args:
691 data: xml blob from client.
692 label: optional label for the update.
693 Returns:
694 Update payload message for client.
695 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700696 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 16:26:10 -0700697 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
698 # be in X-Forwarded-Host.
699 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
700 if x_forwarded_host:
701 self.hostname = 'http://' + x_forwarded_host
702 else:
703 self.hostname = cherrypy.request.base
704
Chris Sosa9841e1c2010-10-14 10:51:45 -0700705 if self.urlbase:
706 static_urlbase = self.urlbase
707 elif self.serve_only:
708 static_urlbase = '%s/static/archive' % self.hostname
709 else:
710 static_urlbase = '%s/static' % self.hostname
711
Don Garrett0ad09372010-12-06 16:20:30 -0800712 # If we have a proxy port, adjust the URL we instruct the client to
713 # use to go through the proxy.
714 if self.proxy_port:
715 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
716
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700717 _Log('Using static url base %s' % static_urlbase)
718 _Log('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700719
Chris Sosa52148582012-11-15 15:35:58 -0800720 protocol, app, event, update_check = autoupdate_lib.ParseUpdateRequest(data)
Jay Srinivasanac69d262012-10-30 19:05:53 -0700721 _Log('Client is using protocol version: %s' % protocol)
Jay Srinivasanac69d262012-10-30 19:05:53 -0700722
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700723 # Determine request IP, strip any IPv6 data for simplicity.
724 client_ip = cherrypy.request.remote.ip.split(':')[-1]
725
Gilad Arnold286a0062012-01-12 13:47:02 -0800726 # Obtain (or init) info object for this client.
727 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
728
729 # Initialize an empty dictionary for event attributes.
730 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700731
732 # Store event details in the host info dictionary for API usage.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700733 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800734 event_result = int(event[0].getAttribute('eventresult'))
735 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700736 client_previous_version = (event[0].getAttribute('previousversion')
737 if event[0].hasAttribute('previousversion')
738 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800739 # Store attributes to legacy host info structure
740 curr_host_info.attrs['last_event_status'] = event_result
741 curr_host_info.attrs['last_event_type'] = event_type
742 # Add attributes to log message
743 log_message['event_result'] = event_result
744 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700745 if client_previous_version is not None:
746 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800747
748 # Get information about the requester.
Chris Sosa52148582012-11-15 15:35:58 -0800749 if app:
750 client_version = app.getAttribute('version')
751 channel = app.getAttribute('track')
752 board_id = (app.hasAttribute('board') and app.getAttribute('board')
753 or self._GetDefaultBoardID())
Gilad Arnold286a0062012-01-12 13:47:02 -0800754 # Add attributes to log message
755 log_message['version'] = client_version
756 log_message['track'] = channel
757 log_message['board'] = board_id
758
Gilad Arnold8318eac2012-10-04 12:52:23 -0700759 # Log host event, if so instructed.
760 if self.host_log:
761 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700762
Chris Sosa0356d3b2010-09-16 15:46:22 -0700763 # We only generate update payloads for updatecheck requests.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700764 if not update_check:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700765 _Log('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700766 # TODO(sosa): Generate correct non-updatecheck payload to better test
767 # update clients.
Chris Sosa52148582012-11-15 15:35:58 -0800768 return autoupdate_lib.GetNoUpdateResponse(protocol)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700769
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700770 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800771 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700772
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700773 # If maximum number of updates already requested, refuse.
774 if self.max_updates > 0:
775 self.max_updates -= 1
776 elif self.max_updates == 0:
Chris Sosa52148582012-11-15 15:35:58 -0800777 return autoupdate_lib.GetNoUpdateResponse(protocol)
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700778
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700779 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 13:47:02 -0800780 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700781 if forced_update:
782 label = forced_update
783
Chris Sosa0356d3b2010-09-16 15:46:22 -0700784 # Separate logic as Factory requests have static url's that override
785 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700786 if self.factory_config:
Jay Srinivasanac69d262012-10-30 19:05:53 -0700787 return self.HandleFactoryRequest(board_id, channel, protocol)
Nick Sanders723f3262010-09-16 05:18:41 -0700788 else:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700789 url = ''
790 # Are we provisioning a remote or local payload?
791 if self.remote_payload:
792 # If no explicit label was provided, use the value of --payload.
793 if not label and self.payload_path:
794 label = self.payload_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700795
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700796 # Form the URL of the update payload. This assumes that the payload
797 # file name is a devserver constant (which currently is the case).
798 url = '/'.join(filter(None, [static_urlbase, label, UPDATE_FILE]))
Chris Sosa5d342a22010-09-28 16:54:41 -0700799
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700800 # Get remote payload attributes.
801 sha1, sha256, file_size, is_delta_format = \
802 self._GetRemotePayloadAttrs(url)
803 else:
804 # Generate payload.
805 static_image_dir = os.path.join(*filter(None, [self.static_dir, label]))
806 payload_path = self.GenerateUpdatePayloadForNonFactory(
807 board_id, client_version, static_image_dir)
808 # If properly generated, obtain the payload URL and attributes.
809 if payload_path:
810 url = '/'.join(filter(None, [static_urlbase, label, payload_path]))
811 sha1, sha256, file_size, is_delta_format = \
812 self._GetLocalPayloadAttrs(static_image_dir, payload_path)
813
814 # If we end up with an actual payload path, generate a response.
815 if url:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700816 _Log('Responding to client to use url %s to get image.' % url)
Chris Sosa52148582012-11-15 15:35:58 -0800817 return autoupdate_lib.GetUpdateResponse(
818 sha1, sha256, file_size, url, is_delta_format, protocol,
819 self.critical_update)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700820 else:
Chris Sosa52148582012-11-15 15:35:58 -0800821 return autoupdate_lib.GetNoUpdateResponse(protocol)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700822
823 def HandleHostInfoPing(self, ip):
824 """Returns host info dictionary for the given IP in JSON format."""
825 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800826 if ip in self.host_infos.table:
827 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
828
829 def HandleHostLogPing(self, ip):
830 """Returns a complete log of events for host in JSON format."""
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700831 # If all events requested, return a dictionary of logs keyed by IP address.
Gilad Arnold286a0062012-01-12 13:47:02 -0800832 if ip == 'all':
833 return json.dumps(
834 dict([(key, self.host_infos.table[key].log)
835 for key in self.host_infos.table]))
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700836
837 # Otherwise we're looking for a specific IP address, so find its log.
Gilad Arnold286a0062012-01-12 13:47:02 -0800838 if ip in self.host_infos.table:
839 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700840
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700841 # If no events were logged for this IP, return an empty log.
842 return json.dumps([])
843
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700844 def HandleSetUpdatePing(self, ip, label):
845 """Sets forced_update_label for a given host."""
846 assert ip, 'No ip provided.'
847 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800848 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label