blob: fe9b9ef4dbfec97ce2a07b444f0f12ecb75216a2 [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
Gilad Arnolde74b3812013-04-22 11:27:38 -07008import sys
Darin Petkov2b2ff4b2010-07-27 15:02:09 -07009import time
Gilad Arnold0c9c8602012-10-02 23:58:58 -070010import urllib2
Don Garrett0ad09372010-12-06 16:20:30 -080011import urlparse
Chris Sosa7c931362010-10-11 19:49:01 -070012
Gilad Arnoldabb352e2012-09-23 01:24:27 -070013import cherrypy
14
Gilad Arnolde74b3812013-04-22 11:27:38 -070015# Allow importing from dev/host/lib when running from source tree.
16lib_dir = os.path.join(os.path.dirname(__file__), 'host', 'lib')
17if os.path.exists(lib_dir) and os.path.isdir(lib_dir):
18 sys.path.insert(1, lib_dir)
19
joychen921e1fb2013-06-28 11:12:20 -070020import build_util
Chris Sosa52148582012-11-15 15:35:58 -080021import autoupdate_lib
Gilad Arnold55a2a372012-10-02 09:46:32 -070022import common_util
joychen7c2054a2013-07-25 11:14:07 -070023import devserver_constants as constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070024import log_util
Gilad Arnolde74b3812013-04-22 11:27:38 -070025# pylint: disable=F0401
26import update_payload
Chris Sosa05491b12010-11-08 17:14:16 -080027
Gilad Arnoldc65330c2012-09-20 15:17:48 -070028
29# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080030def _Log(message, *args):
31 return log_util.LogWithTag('UPDATE', message, *args)
Gilad Arnoldc65330c2012-09-20 15:17:48 -070032
rtc@google.comded22402009-10-26 22:36:21 +000033
Chris Sosa417e55d2011-01-25 16:40:48 -080034UPDATE_FILE = 'update.gz'
Chris Sosa6a3697f2013-01-29 16:44:43 -080035METADATA_FILE = 'update.meta'
Chris Sosa417e55d2011-01-25 16:40:48 -080036STATEFUL_FILE = 'stateful.tgz'
37CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 15:46:22 -070038
Don Garrett0ad09372010-12-06 16:20:30 -080039
Gilad Arnold0c9c8602012-10-02 23:58:58 -070040class AutoupdateError(Exception):
41 """Exception classes used by this module."""
42 pass
43
44
Don Garrett0ad09372010-12-06 16:20:30 -080045def _ChangeUrlPort(url, new_port):
46 """Return the URL passed in with a different port"""
47 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
48 host_port = netloc.split(':')
49
50 if len(host_port) == 1:
51 host_port.append(new_port)
52 else:
53 host_port[1] = new_port
54
55 print host_port
56 netloc = "%s:%s" % tuple(host_port)
57
58 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
59
Chris Sosa6a3697f2013-01-29 16:44:43 -080060def _NonePathJoin(*args):
61 """os.path.join that filters None's from the argument list."""
62 return os.path.join(*filter(None, args))
Don Garrett0ad09372010-12-06 16:20:30 -080063
Chris Sosa6a3697f2013-01-29 16:44:43 -080064
65class HostInfo(object):
Gilad Arnold286a0062012-01-12 13:47:02 -080066 """Records information about an individual host.
67
68 Members:
69 attrs: Static attributes (legacy)
70 log: Complete log of recorded client entries
71 """
72
73 def __init__(self):
74 # A dictionary of current attributes pertaining to the host.
75 self.attrs = {}
76
77 # A list of pairs consisting of a timestamp and a dictionary of recorded
78 # attributes.
79 self.log = []
80
81 def __repr__(self):
82 return 'attrs=%s, log=%s' % (self.attrs, self.log)
83
84 def AddLogEntry(self, entry):
85 """Append a new log entry."""
86 # Append a timestamp.
87 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
88 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
89 # Add entry to hosts' message log.
90 self.log.append(entry)
91
Gilad Arnold286a0062012-01-12 13:47:02 -080092
Chris Sosa6a3697f2013-01-29 16:44:43 -080093class HostInfoTable(object):
Gilad Arnold286a0062012-01-12 13:47:02 -080094 """Records information about a set of hosts who engage in update activity.
95
96 Members:
97 table: Table of information on hosts.
98 """
99
100 def __init__(self):
101 # A dictionary of host information. Keys are normally IP addresses.
102 self.table = {}
103
104 def __repr__(self):
105 return '%s' % self.table
106
107 def GetInitHostInfo(self, host_id):
108 """Return a host's info object, or create a new one if none exists."""
109 return self.table.setdefault(host_id, HostInfo())
110
111 def GetHostInfo(self, host_id):
112 """Return an info object for given host, if such exists."""
Chris Sosa1885d032012-11-29 17:07:27 -0800113 return self.table.get(host_id)
Gilad Arnold286a0062012-01-12 13:47:02 -0800114
115
Chris Sosa6a3697f2013-01-29 16:44:43 -0800116class UpdateMetadata(object):
117 """Object containing metadata about an update payload."""
118
119 def __init__(self, sha1, sha256, size, is_delta_format):
120 self.sha1 = sha1
121 self.sha256 = sha256
122 self.size = size
123 self.is_delta_format = is_delta_format
124
125
joychen921e1fb2013-06-28 11:12:20 -0700126class Autoupdate(build_util.BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700127 """Class that contains functionality that handles Chrome OS update pings.
128
129 Members:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700130 use_test_image: use chromiumos_test_image.bin rather than the standard.
131 urlbase: base URL, other than devserver, for update images.
132 forced_image: path to an image to use for all updates.
133 payload_path: path to pre-generated payload to serve.
134 src_image: if specified, creates a delta payload from this image.
135 proxy_port: port of local proxy to tell client to connect to you
136 through.
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700137 patch_kernel: Patch the kernel when generating updates
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700138 board: board for the image. Needed for pre-generating of updates.
139 copy_to_static_root: copies images generated from the cache to ~/static.
140 private_key: path to private key in PEM format.
Gilad Arnold8318eac2012-10-04 12:52:23 -0700141 critical_update: whether provisioned payload is critical.
142 remote_payload: whether provisioned payload is remotely staged.
143 max_updates: maximum number of updates we'll try to provision.
144 host_log: record full history of host update events.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700145 """
rtc@google.comded22402009-10-26 22:36:21 +0000146
joychened64b222013-06-21 16:39:34 -0700147 _OLD_PAYLOAD_URL_PREFIX = '/static/archive'
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700148 _PAYLOAD_URL_PREFIX = '/static/'
149 _FILEINFO_URL_PREFIX = '/api/fileinfo/'
150
Chris Sosa6a3697f2013-01-29 16:44:43 -0800151 SHA1_ATTR = 'sha1'
152 SHA256_ATTR = 'sha256'
153 SIZE_ATTR = 'size'
154 ISDELTA_ATTR = 'is_delta'
155
joychen7c2054a2013-07-25 11:14:07 -0700156 def __init__(self, test_image=False, urlbase=None,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700157 forced_image=None, payload_path=None,
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700158 proxy_port=None, src_image='', patch_kernel=True, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800159 copy_to_static_root=True, private_key=None,
Chris Sosa52148582012-11-15 15:35:58 -0800160 critical_update=False, remote_payload=False, max_updates= -1,
Chris Sosa6a3697f2013-01-29 16:44:43 -0800161 host_log=False, *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700162 super(Autoupdate, self).__init__(*args, **kwargs)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700163 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -0700164 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700165 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700166 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700167 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700168
Chris Sosa0356d3b2010-09-16 15:46:22 -0700169 self.forced_image = forced_image
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700170 self.payload_path = payload_path
Chris Sosa62f720b2010-10-26 21:39:48 -0700171 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800172 self.proxy_port = proxy_port
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700173 self.patch_kernel = patch_kernel
Chris Sosae67b78f2010-11-04 17:33:16 -0700174 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800175 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800176 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800177 self.critical_update = critical_update
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700178 self.remote_payload = remote_payload
Jay Srinivasanac69d262012-10-30 19:05:53 -0700179 self.max_updates = max_updates
Gilad Arnold8318eac2012-10-04 12:52:23 -0700180 self.host_log = host_log
Don Garrettfff4c322010-11-19 13:37:12 -0800181
Chris Sosa417e55d2011-01-25 16:40:48 -0800182 # Path to pre-generated file.
183 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700184
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700185 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800186 # information about a given host. A host is identified by its IP address.
187 # The info stored for each host includes a complete log of events for this
188 # host, as well as a dictionary of current attributes derived from events.
189 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700190
Chris Sosa6a3697f2013-01-29 16:44:43 -0800191 @classmethod
192 def _ReadMetadataFromStream(cls, stream):
193 """Returns metadata obj from input json stream that implements .read()."""
194 file_attr_dict = {}
195 try:
196 file_attr_dict = json.loads(stream.read())
197 except IOError:
198 return None
199
200 sha1 = file_attr_dict.get(cls.SHA1_ATTR)
201 sha256 = file_attr_dict.get(cls.SHA256_ATTR)
202 size = file_attr_dict.get(cls.SIZE_ATTR)
203 is_delta = file_attr_dict.get(cls.ISDELTA_ATTR)
204 return UpdateMetadata(sha1, sha256, size, is_delta)
205
206 @staticmethod
207 def _ReadMetadataFromFile(payload_dir):
208 """Returns metadata object from the metadata_file in the payload_dir"""
209 metadata_file = os.path.join(payload_dir, METADATA_FILE)
210 if os.path.exists(metadata_file):
211 with open(metadata_file, 'r') as metadata_stream:
212 return Autoupdate._ReadMetadataFromStream(metadata_stream)
213
214 @classmethod
215 def _StoreMetadataToFile(cls, payload_dir, metadata_obj):
216 """Stores metadata object into the metadata_file of the payload_dir"""
217 file_dict = {cls.SHA1_ATTR: metadata_obj.sha1,
218 cls.SHA256_ATTR: metadata_obj.sha256,
219 cls.SIZE_ATTR: metadata_obj.size,
220 cls.ISDELTA_ATTR: metadata_obj.is_delta_format}
221 metadata_file = os.path.join(payload_dir, METADATA_FILE)
222 with open(metadata_file, 'w') as file_handle:
223 json.dump(file_dict, file_handle)
224
Chris Sosa0356d3b2010-09-16 15:46:22 -0700225 def _GetDefaultBoardID(self):
226 """Returns the default board id stored in .default_board."""
227 board_file = '%s/.default_board' % (self.scripts_dir)
228 try:
229 return open(board_file).read()
230 except IOError:
231 return 'x86-generic'
232
Chris Sosa52148582012-11-15 15:35:58 -0800233 @staticmethod
234 def _GetVersionFromDir(image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700235 """Returns the version of the image based on the name of the directory."""
236 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700237 parts = latest_version.split('-')
238 if len(parts) == 2:
239 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
240 # TODO(derat): Remove the code for old-style versions after 20120101.
241 return parts[0]
242 else:
243 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
244 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700245
Chris Sosa52148582012-11-15 15:35:58 -0800246 @staticmethod
247 def _CanUpdate(client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800248 """Returns true if the latest_version is greater than the client_version.
249 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800250 _Log('client version %s latest version %s', client_version, latest_version)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700251
252 client_tokens = client_version.replace('_', '').split('.')
253 # If the client has an old four-token version like "0.16.892.0", drop the
254 # first two tokens -- we use versions like "892.0.0" now.
255 # TODO(derat): Remove the code for old-style versions after 20120101.
256 if len(client_tokens) == 4:
257 client_tokens = client_tokens[2:]
258
259 latest_tokens = latest_version.replace('_', '').split('.')
260 if len(latest_tokens) == 4:
261 latest_tokens = latest_tokens[2:]
262
263 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700264 if int(latest_tokens[i]) == int(client_tokens[i]):
265 continue
266 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700267
268 # Favor four-token new-style versions on the server over old-style versions
269 # on the client if everything else matches.
270 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700271
Chris Sosa0356d3b2010-09-16 15:46:22 -0700272 def _GetImageName(self):
273 """Returns the name of the image that should be used."""
274 if self.use_test_image:
275 image_name = 'chromiumos_test_image.bin'
276 else:
277 image_name = 'chromiumos_image.bin'
Chris Sosa6a3697f2013-01-29 16:44:43 -0800278
Chris Sosa0356d3b2010-09-16 15:46:22 -0700279 return image_name
280
Chris Sosa52148582012-11-15 15:35:58 -0800281 @staticmethod
Gilad Arnolde74b3812013-04-22 11:27:38 -0700282 def IsDeltaFormatFile(filename):
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700283 try:
Gilad Arnolde74b3812013-04-22 11:27:38 -0700284 with open(filename) as payload_file:
285 payload = update_payload.Payload(payload_file)
286 payload.Init()
287 return payload.IsDelta()
288 except (IOError, update_payload.PayloadError):
289 # For unit tests we may not have real files, so it's ok to ignore these
290 # errors.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700291 return False
292
Don Garrettf90edf02010-11-16 17:36:14 -0800293 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700294 """Generates an update gz given a full path to an image.
295
296 Args:
297 image_path: Full path to image.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800298 Raises:
299 subprocess.CalledProcessError if the update generator fails to generate a
300 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700301 """
joychen7c2054a2013-07-25 11:14:07 -0700302 update_path = os.path.join(output_dir, constants.UPDATE_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800303 _Log('Generating update image %s', update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700304
Chris Sosa0f1ec842011-02-14 16:33:22 -0800305 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700306 'cros_generate_update_payload',
Chris Sosa6a3697f2013-01-29 16:44:43 -0800307 '--image', image_path,
308 '--output', update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800309 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700310
Chris Sosa52148582012-11-15 15:35:58 -0800311 if src_image:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800312 update_command.extend(['--src_image', src_image])
Chris Sosa52148582012-11-15 15:35:58 -0800313
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700314 if self.patch_kernel:
Chris Sosa52148582012-11-15 15:35:58 -0800315 update_command.append('--patch_kernel')
316
317 if self.private_key:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800318 update_command.extend(['--private_key', self.private_key])
Chris Sosa0f1ec842011-02-14 16:33:22 -0800319
Chris Sosa6a3697f2013-01-29 16:44:43 -0800320 _Log('Running %s', ' '.join(update_command))
321 subprocess.check_call(update_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700322
Chris Sosa52148582012-11-15 15:35:58 -0800323 @staticmethod
324 def GenerateStatefulFile(image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800325 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700326
327 Args:
328 image_path: Full path to image.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800329 Raises:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800330 subprocess.CalledProcessError if the update generator fails to generate a
Chris Sosa908fd6f2010-11-10 17:31:18 -0800331 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700332 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800333 update_command = [
334 'cros_generate_stateful_update_payload',
335 '--image', image_path,
336 '--output_dir', output_dir,
337 ]
338 _Log('Running %s', ' '.join(update_command))
339 subprocess.check_call(update_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700340
Don Garrettf90edf02010-11-16 17:36:14 -0800341 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
342 """Find directory to store a cached update.
343
Gilad Arnold55a2a372012-10-02 09:46:32 -0700344 Given one, or two images for an update, this finds which cache directory
345 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-16 17:36:14 -0800346
Gilad Arnold55a2a372012-10-02 09:46:32 -0700347 Returns:
348 A directory path for storing a cached update, of the following form:
349 Non-delta updates:
350 CACHE_DIR/<dest_hash>
351 Delta updates:
352 CACHE_DIR/<src_hash>_<dest_hash>
353 Signed updates (self.private_key):
354 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-07 19:32:50 -0700355 """
Gilad Arnold55a2a372012-10-02 09:46:32 -0700356 update_dir = ''
Chris Sosa744e1472011-09-07 19:32:50 -0700357 if src_image:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700358 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-16 17:36:14 -0800359
Gilad Arnold55a2a372012-10-02 09:46:32 -0700360 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-07 19:32:50 -0700361 if self.private_key:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700362 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-07 19:32:50 -0700363
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700364 if self.patch_kernel:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700365 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 10:15:47 -0800366
Gilad Arnold55a2a372012-10-02 09:46:32 -0700367 return os.path.join(CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800368
Don Garrettfff4c322010-11-19 13:37:12 -0800369 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800370 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700371
Chris Sosade91f672010-11-16 10:05:44 -0800372 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800373 src_image: image we are updating from (Null/empty for non-delta)
374 image_path: full path to the image.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800375 output_dir: the directory to write the update payloads to
376 Raises:
377 AutoupdateError if it failed to generate either update or stateful
378 payload.
Chris Sosade91f672010-11-16 10:05:44 -0800379 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800380 _Log('Generating update for image %s', image_path)
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700381
Chris Sosa6a3697f2013-01-29 16:44:43 -0800382 # Delete any previous state in this directory.
383 os.system('rm -rf "%s"' % output_dir)
384 os.makedirs(output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000385
Chris Sosa6a3697f2013-01-29 16:44:43 -0800386 try:
387 self.GenerateUpdateFile(self.src_image, image_path, output_dir)
388 self.GenerateStatefulFile(image_path, output_dir)
389 except subprocess.CalledProcessError:
390 os.system('rm -rf "%s"' % output_dir)
391 raise AutoupdateError('Failed to generate update in %s' % output_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800392
393 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
394 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000395
Chris Sosa0356d3b2010-09-16 15:46:22 -0700396 Args:
397 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700398 static_image_dir: the directory to move images to after generating.
399 Returns:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800400 update directory relative to static_image_dir. None if it should
401 serve from the static_image_dir.
402 Raises:
403 AutoupdateError if it we need to generate a payload and fail to do so.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700404 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800405 _Log('Generating update for src %s image %s', self.src_image, image_path)
Chris Sosae67b78f2010-11-04 17:33:16 -0700406
Chris Sosa417e55d2011-01-25 16:40:48 -0800407 # If it was pregenerated_path, don't regenerate
408 if self.pregenerated_path:
409 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800410
Don Garrettf90edf02010-11-16 17:36:14 -0800411 # Which sub_dir of static_image_dir should hold our cached update image
412 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800413 _Log('Caching in sub_dir "%s"', cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800414
Don Garrettf90edf02010-11-16 17:36:14 -0800415 # The cached payloads exist in a cache dir
416 cache_update_payload = os.path.join(static_image_dir,
joychen7c2054a2013-07-25 11:14:07 -0700417 cache_sub_dir,
418 constants.UPDATE_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800419 cache_stateful_payload = os.path.join(static_image_dir,
Chris Sosa6a3697f2013-01-29 16:44:43 -0800420 cache_sub_dir, STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800421
Chris Sosa6a3697f2013-01-29 16:44:43 -0800422 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800423 # Check to see if this cache directory is valid.
424 if not os.path.exists(cache_update_payload) or not os.path.exists(
425 cache_stateful_payload):
Chris Sosa6a3697f2013-01-29 16:44:43 -0800426 self.GenerateUpdateImage(image_path, full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800427
Chris Sosa6a3697f2013-01-29 16:44:43 -0800428 self.pregenerated_path = cache_sub_dir
Chris Sosa65d339b2013-01-21 18:59:21 -0800429
Chris Sosa6a3697f2013-01-29 16:44:43 -0800430 # Generate the cache file.
431 self.GetLocalPayloadAttrs(full_cache_dir)
432 cache_metadata_file = os.path.join(full_cache_dir, METADATA_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800433
Chris Sosa08d55a22011-01-19 16:08:02 -0800434 # Generation complete, copy if requested.
435 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800436 # The final results exist directly in static
joychen7c2054a2013-07-25 11:14:07 -0700437 cros_update_payload = os.path.join(static_image_dir,
438 constants.UPDATE_FILE)
Gilad Arnolde74b3812013-04-22 11:27:38 -0700439 stateful_payload = os.path.join(static_image_dir, STATEFUL_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800440 metadata_file = os.path.join(static_image_dir, METADATA_FILE)
Gilad Arnolde74b3812013-04-22 11:27:38 -0700441 common_util.CopyFile(cache_update_payload, cros_update_payload)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700442 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800443 common_util.CopyFile(cache_metadata_file, metadata_file)
444 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800445 else:
446 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700447
Chris Sosa6a3697f2013-01-29 16:44:43 -0800448 def GenerateLatestUpdateImage(self, board, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800449 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700450 """Generates an update using the latest image that has been built.
451
452 This will only generate an update if the newest update is newer than that
453 on the client or client_version is 'ForcedUpdate'.
454
455 Args:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800456 board: Name of the board.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700457 client_version: Current version of the client or 'ForcedUpdate'
458 static_image_dir: the directory to move images to after generating.
459 Returns:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800460 Name of the update directory relative to the static dir. None if it should
461 serve from the static_image_dir.
462 Raises:
463 AutoupdateError if it failed to generate the payload or can't update
464 the given client_version.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700465 """
joychen921e1fb2013-06-28 11:12:20 -0700466 latest_image_dir = self.GetLatestImageDir(board)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700467 latest_version = self._GetVersionFromDir(latest_image_dir)
468 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
469
Chris Sosa0356d3b2010-09-16 15:46:22 -0700470 # Check to see whether or not we should update.
471 if client_version != 'ForcedUpdate' and not self._CanUpdate(
472 client_version, latest_version):
Chris Sosa6a3697f2013-01-29 16:44:43 -0800473 raise AutoupdateError('Update check received but no update available '
474 'for client')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700475
Don Garrettf90edf02010-11-16 17:36:14 -0800476 return self.GenerateUpdateImageWithCache(latest_image_path,
477 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700478
Chris Sosa6a3697f2013-01-29 16:44:43 -0800479 def GenerateUpdatePayload(self, board, client_version, static_image_dir):
480 """Generates an update for an image and returns the relative payload dir.
Chris Sosaa73ec162010-05-03 20:18:02 -0700481
Chris Sosa6a3697f2013-01-29 16:44:43 -0800482 Returns:
483 payload dir relative to static_image_dir. None if it should
484 serve from the static_image_dir.
485 Raises:
486 AutoupdateError if it failed to generate the payload.
Don Garrettf90edf02010-11-16 17:36:14 -0800487 """
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700488 if self.payload_path:
joychen7c2054a2013-07-25 11:14:07 -0700489 dest_path = os.path.join(static_image_dir, constants.UPDATE_FILE)
490 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
491
Don Garrett0c880e22010-11-17 18:13:37 -0800492 # If the forced payload is not already in our static_image_dir,
493 # copy it there.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700494 src_path = os.path.abspath(self.payload_path)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800495 src_stateful = os.path.join(os.path.dirname(src_path), STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800496 # Only copy the files if the source directory is different from dest.
497 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700498 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 12:09:35 -0800499
500 # The stateful payload is optional.
501 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700502 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 12:09:35 -0800503 else:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800504 _Log('WARN: %s not found. Expected for dev and test builds',
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700505 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800506 if os.path.exists(dest_stateful):
507 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800508
Chris Sosa6a3697f2013-01-29 16:44:43 -0800509 # Serve from the main directory so rel_path is None.
510 return None
Don Garrett0c880e22010-11-17 18:13:37 -0800511 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800512 return self.GenerateUpdateImageWithCache(
513 self.forced_image,
514 static_image_dir=static_image_dir)
Chris Sosa65d339b2013-01-21 18:59:21 -0800515 else:
joychen7c2054a2013-07-25 11:14:07 -0700516 update_path = os.path.join(static_image_dir, constants.UPDATE_FILE)
517 # if a label was specified, check if the update file is there.
518 if static_image_dir != self.static_dir and os.path.exists(update_path):
519 return None
520
Chris Sosa6a3697f2013-01-29 16:44:43 -0800521 if not board:
522 raise AutoupdateError(
523 'Failed to generate update. '
524 'You must set --board when pre-generating latest update.')
Chris Sosa65d339b2013-01-21 18:59:21 -0800525
Chris Sosa6a3697f2013-01-29 16:44:43 -0800526 return self.GenerateLatestUpdateImage(board, client_version,
527 static_image_dir)
Chris Sosa2c048f12010-10-27 16:05:27 -0700528
529 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800530 """Pre-generates an update and prints out the relative path it.
531
Chris Sosa6a3697f2013-01-29 16:44:43 -0800532 Returns relative path of the update.
Chris Sosa65d339b2013-01-21 18:59:21 -0800533
Chris Sosa6a3697f2013-01-29 16:44:43 -0800534 Raises:
535 AutoupdateError if it failed to generate the payload.
536 """
537 _Log('Pre-generating the update payload')
538 # Does not work with labels so just use static dir.
539 pregenerated_update = self.GenerateUpdatePayload(self.board, '0.0.0.0',
540 self.static_dir)
541 print 'PREGENERATED_UPDATE=%s' % _NonePathJoin(pregenerated_update,
joychen7c2054a2013-07-25 11:14:07 -0700542 constants.UPDATE_FILE)
Chris Sosa417e55d2011-01-25 16:40:48 -0800543 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700544
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700545 def _GetRemotePayloadAttrs(self, url):
546 """Returns hashes, size and delta flag of a remote update payload.
547
548 Obtain attributes of a payload file available on a remote devserver. This
549 is based on the assumption that the payload URL uses the /static prefix. We
550 need to make sure that both clients (requests) and remote devserver
551 (provisioning) preserve this invariant.
552
553 Args:
554 url: URL of statically staged remote file (http://host:port/static/...)
555 Returns:
556 A tuple containing the SHA1, SHA256, file size and whether or not it's a
557 delta payload (Boolean).
558 """
559 if self._PAYLOAD_URL_PREFIX not in url:
560 raise AutoupdateError(
561 'Payload URL does not have the expected prefix (%s)' %
562 self._PAYLOAD_URL_PREFIX)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800563
joychened64b222013-06-21 16:39:34 -0700564 if self._OLD_PAYLOAD_URL_PREFIX in url:
565 fileinfo_url = url.replace(self._OLD_PAYLOAD_URL_PREFIX,
566 self._FILEINFO_URL_PREFIX)
567 else:
568 fileinfo_url = url.replace(self._PAYLOAD_URL_PREFIX,
569 self._FILEINFO_URL_PREFIX)
570
Chris Sosa6a3697f2013-01-29 16:44:43 -0800571 _Log('Retrieving file info for remote payload via %s', fileinfo_url)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700572 try:
573 conn = urllib2.urlopen(fileinfo_url)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800574 metadata_obj = Autoupdate._ReadMetadataFromStream(conn)
575 # These fields are required for remote calls.
576 if not metadata_obj:
577 raise AutoupdateError('Failed to obtain remote payload info')
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700578
Chris Sosa6a3697f2013-01-29 16:44:43 -0800579 return metadata_obj
580 except IOError as e:
581 raise AutoupdateError('Failed to obtain remote payload info: %s', e)
582
583 def GetLocalPayloadAttrs(self, payload_dir):
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700584 """Returns hashes, size and delta flag of a local update payload.
585
586 Args:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800587 payload_dir: Path to the directory the payload is in.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700588 Returns:
589 A tuple containing the SHA1, SHA256, file size and whether or not it's a
590 delta payload (Boolean).
591 """
joychen7c2054a2013-07-25 11:14:07 -0700592 filename = os.path.join(payload_dir, constants.UPDATE_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800593 if not os.path.exists(filename):
594 raise AutoupdateError('update.gz not present in payload dir %s' %
595 payload_dir)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700596
Chris Sosa6a3697f2013-01-29 16:44:43 -0800597 metadata_obj = Autoupdate._ReadMetadataFromFile(payload_dir)
598 if not metadata_obj or not (metadata_obj.sha1 and
599 metadata_obj.sha256 and
600 metadata_obj.size):
601 sha1 = common_util.GetFileSha1(filename)
602 sha256 = common_util.GetFileSha256(filename)
603 size = common_util.GetFileSize(filename)
Gilad Arnolde74b3812013-04-22 11:27:38 -0700604 is_delta_format = self.IsDeltaFormatFile(filename)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800605 metadata_obj = UpdateMetadata(sha1, sha256, size, is_delta_format)
606 Autoupdate._StoreMetadataToFile(payload_dir, metadata_obj)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700607
Chris Sosa6a3697f2013-01-29 16:44:43 -0800608 return metadata_obj
609
610 def _ProcessUpdateComponents(self, app, event):
611 """Processes the app and event components of an update request.
612
613 Returns tuple containing forced_update_label, client_version, and board.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700614 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800615 # Initialize an empty dictionary for event attributes to log.
616 log_message = {}
Jay Srinivasanac69d262012-10-30 19:05:53 -0700617
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700618 # Determine request IP, strip any IPv6 data for simplicity.
619 client_ip = cherrypy.request.remote.ip.split(':')[-1]
Gilad Arnold286a0062012-01-12 13:47:02 -0800620 # Obtain (or init) info object for this client.
621 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
622
Chris Sosa6a3697f2013-01-29 16:44:43 -0800623 client_version = 'ForcedUpdate'
624 board = None
625 if app:
626 client_version = app.getAttribute('version')
627 channel = app.getAttribute('track')
628 board = (app.hasAttribute('board') and app.getAttribute('board')
629 or self._GetDefaultBoardID())
630 # Add attributes to log message
631 log_message['version'] = client_version
632 log_message['track'] = channel
633 log_message['board'] = board
634 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700635
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700636 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800637 event_result = int(event[0].getAttribute('eventresult'))
638 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700639 client_previous_version = (event[0].getAttribute('previousversion')
640 if event[0].hasAttribute('previousversion')
641 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800642 # Store attributes to legacy host info structure
643 curr_host_info.attrs['last_event_status'] = event_result
644 curr_host_info.attrs['last_event_type'] = event_type
645 # Add attributes to log message
646 log_message['event_result'] = event_result
647 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700648 if client_previous_version is not None:
649 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800650
Gilad Arnold8318eac2012-10-04 12:52:23 -0700651 # Log host event, if so instructed.
652 if self.host_log:
653 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700654
Chris Sosa6a3697f2013-01-29 16:44:43 -0800655 return (curr_host_info.attrs.pop('forced_update_label', None),
656 client_version, board)
657
658 def _GetStaticUrl(self):
659 """Returns the static url base that should prefix all payload responses."""
660 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
661 if x_forwarded_host:
662 hostname = 'http://' + x_forwarded_host
663 else:
664 hostname = cherrypy.request.base
665
666 if self.urlbase:
667 static_urlbase = self.urlbase
Chris Sosa6a3697f2013-01-29 16:44:43 -0800668 else:
669 static_urlbase = '%s/static' % hostname
670
671 # If we have a proxy port, adjust the URL we instruct the client to
672 # use to go through the proxy.
673 if self.proxy_port:
674 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
675
676 _Log('Using static url base %s', static_urlbase)
677 _Log('Handling update ping as %s', hostname)
678 return static_urlbase
679
680 def HandleUpdatePing(self, data, label=None):
681 """Handles an update ping from an update client.
682
683 Args:
684 data: XML blob from client.
685 label: optional label for the update.
686 Returns:
687 Update payload message for client.
688 """
689 # Get the static url base that will form that base of our update url e.g.
690 # http://hostname:8080/static/update.gz.
691 static_urlbase = self._GetStaticUrl()
692
693 # Parse the XML we got into the components we care about.
694 protocol, app, event, update_check = autoupdate_lib.ParseUpdateRequest(data)
695
696 # #########################################################################
697 # Process attributes of the update check.
698 forced_update_label, client_version, board = self._ProcessUpdateComponents(
699 app, event)
700
701 # We only process update_checks in the update rpc.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700702 if not update_check:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800703 _Log('Non-update check received. Returning blank payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700704 # TODO(sosa): Generate correct non-updatecheck payload to better test
705 # update clients.
Chris Sosa52148582012-11-15 15:35:58 -0800706 return autoupdate_lib.GetNoUpdateResponse(protocol)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700707
Chris Sosa6a3697f2013-01-29 16:44:43 -0800708 # In case max_updates is used, return no response if max reached.
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700709 if self.max_updates > 0:
710 self.max_updates -= 1
711 elif self.max_updates == 0:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800712 _Log('Request received but max number of updates handled')
Chris Sosa52148582012-11-15 15:35:58 -0800713 return autoupdate_lib.GetNoUpdateResponse(protocol)
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700714
Chris Sosa6a3697f2013-01-29 16:44:43 -0800715 _Log('Update Check Received. Client is using protocol version: %s',
716 protocol)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700717
Chris Sosa6a3697f2013-01-29 16:44:43 -0800718 if forced_update_label:
719 if label:
720 _Log('Label: %s set but being overwritten to %s by request', label,
721 forced_update_label)
722
723 label = forced_update_label
724
725 # #########################################################################
726 # Finally its time to generate the omaha response to give to client that
727 # lets them know where to find the payload and its associated metadata.
728 metadata_obj = None
729
730 try:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700731 # Are we provisioning a remote or local payload?
732 if self.remote_payload:
733 # If no explicit label was provided, use the value of --payload.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800734 if not label:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700735 label = self.payload_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700736
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700737 # Form the URL of the update payload. This assumes that the payload
738 # file name is a devserver constant (which currently is the case).
joychen7c2054a2013-07-25 11:14:07 -0700739 url = _NonePathJoin(static_urlbase, label, constants.UPDATE_FILE)
Chris Sosa5d342a22010-09-28 16:54:41 -0700740
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700741 # Get remote payload attributes.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800742 metadata_obj = self._GetRemotePayloadAttrs(url)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700743 else:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800744 static_image_dir = _NonePathJoin(self.static_dir, label)
745 rel_path = None
joychen7c2054a2013-07-25 11:14:07 -0700746 url = _NonePathJoin(static_urlbase, label, rel_path,
747 constants.UPDATE_FILE)
748 if common_util.IsInsideChroot():
Chris Sosa6a3697f2013-01-29 16:44:43 -0800749 rel_path = self.GenerateUpdatePayload(board, client_version,
750 static_image_dir)
joychen7c2054a2013-07-25 11:14:07 -0700751 url = _NonePathJoin(static_urlbase, label, rel_path,
752 constants.UPDATE_FILE)
753 elif not os.path.exists(url):
754 # the update payload wasn't found. This update can't happen.
755 raise AutoupdateError("Failed to find an update payload at %s", url)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800756
Chris Sosa6a3697f2013-01-29 16:44:43 -0800757 local_payload_dir = _NonePathJoin(static_image_dir, rel_path)
758 metadata_obj = self.GetLocalPayloadAttrs(local_payload_dir)
759
760 except AutoupdateError as e:
761 # Raised if we fail to generate an update payload.
762 _Log('Failed to process an update: %r', e)
763 return autoupdate_lib.GetNoUpdateResponse(protocol)
764
765 _Log('Responding to client to use url %s to get image', url)
766 return autoupdate_lib.GetUpdateResponse(
767 metadata_obj.sha1, metadata_obj.sha256, metadata_obj.size, url,
768 metadata_obj.is_delta_format, protocol, self.critical_update)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700769
770 def HandleHostInfoPing(self, ip):
771 """Returns host info dictionary for the given IP in JSON format."""
772 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800773 if ip in self.host_infos.table:
774 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
775
776 def HandleHostLogPing(self, ip):
777 """Returns a complete log of events for host in JSON format."""
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700778 # If all events requested, return a dictionary of logs keyed by IP address.
Gilad Arnold286a0062012-01-12 13:47:02 -0800779 if ip == 'all':
780 return json.dumps(
781 dict([(key, self.host_infos.table[key].log)
782 for key in self.host_infos.table]))
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700783
784 # Otherwise we're looking for a specific IP address, so find its log.
Gilad Arnold286a0062012-01-12 13:47:02 -0800785 if ip in self.host_infos.table:
786 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700787
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700788 # If no events were logged for this IP, return an empty log.
789 return json.dumps([])
790
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700791 def HandleSetUpdatePing(self, ip, label):
792 """Sets forced_update_label for a given host."""
793 assert ip, 'No ip provided.'
794 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800795 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label