blob: 2e4bb6a0f771c3d3f7a420f5c944f0887bd357f3 [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
Gilad Arnold0c9c8602012-10-02 23:58:58 -070034class AutoupdateError(Exception):
35 """Exception classes used by this module."""
36 pass
37
38
Don Garrett0ad09372010-12-06 16:20:30 -080039def _ChangeUrlPort(url, new_port):
40 """Return the URL passed in with a different port"""
41 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
42 host_port = netloc.split(':')
43
44 if len(host_port) == 1:
45 host_port.append(new_port)
46 else:
47 host_port[1] = new_port
48
49 print host_port
50 netloc = "%s:%s" % tuple(host_port)
51
52 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
53
Chris Sosa6a3697f2013-01-29 16:44:43 -080054def _NonePathJoin(*args):
55 """os.path.join that filters None's from the argument list."""
56 return os.path.join(*filter(None, args))
Don Garrett0ad09372010-12-06 16:20:30 -080057
Chris Sosa6a3697f2013-01-29 16:44:43 -080058
59class HostInfo(object):
Gilad Arnold286a0062012-01-12 13:47:02 -080060 """Records information about an individual host.
61
62 Members:
63 attrs: Static attributes (legacy)
64 log: Complete log of recorded client entries
65 """
66
67 def __init__(self):
68 # A dictionary of current attributes pertaining to the host.
69 self.attrs = {}
70
71 # A list of pairs consisting of a timestamp and a dictionary of recorded
72 # attributes.
73 self.log = []
74
75 def __repr__(self):
76 return 'attrs=%s, log=%s' % (self.attrs, self.log)
77
78 def AddLogEntry(self, entry):
79 """Append a new log entry."""
80 # Append a timestamp.
81 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
82 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
83 # Add entry to hosts' message log.
84 self.log.append(entry)
85
Gilad Arnold286a0062012-01-12 13:47:02 -080086
Chris Sosa6a3697f2013-01-29 16:44:43 -080087class HostInfoTable(object):
Gilad Arnold286a0062012-01-12 13:47:02 -080088 """Records information about a set of hosts who engage in update activity.
89
90 Members:
91 table: Table of information on hosts.
92 """
93
94 def __init__(self):
95 # A dictionary of host information. Keys are normally IP addresses.
96 self.table = {}
97
98 def __repr__(self):
99 return '%s' % self.table
100
101 def GetInitHostInfo(self, host_id):
102 """Return a host's info object, or create a new one if none exists."""
103 return self.table.setdefault(host_id, HostInfo())
104
105 def GetHostInfo(self, host_id):
106 """Return an info object for given host, if such exists."""
Chris Sosa1885d032012-11-29 17:07:27 -0800107 return self.table.get(host_id)
Gilad Arnold286a0062012-01-12 13:47:02 -0800108
109
Chris Sosa6a3697f2013-01-29 16:44:43 -0800110class UpdateMetadata(object):
111 """Object containing metadata about an update payload."""
112
113 def __init__(self, sha1, sha256, size, is_delta_format):
114 self.sha1 = sha1
115 self.sha256 = sha256
116 self.size = size
117 self.is_delta_format = is_delta_format
118
119
joychen921e1fb2013-06-28 11:12:20 -0700120class Autoupdate(build_util.BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700121 """Class that contains functionality that handles Chrome OS update pings.
122
123 Members:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700124 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.
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700131 patch_kernel: Patch the kernel when generating updates
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700132 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
joychened64b222013-06-21 16:39:34 -0700141 _OLD_PAYLOAD_URL_PREFIX = '/static/archive'
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700142 _PAYLOAD_URL_PREFIX = '/static/'
143 _FILEINFO_URL_PREFIX = '/api/fileinfo/'
144
Chris Sosa6a3697f2013-01-29 16:44:43 -0800145 SHA1_ATTR = 'sha1'
146 SHA256_ATTR = 'sha256'
147 SIZE_ATTR = 'size'
148 ISDELTA_ATTR = 'is_delta'
149
joychen7c2054a2013-07-25 11:14:07 -0700150 def __init__(self, test_image=False, urlbase=None,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700151 forced_image=None, payload_path=None,
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700152 proxy_port=None, src_image='', patch_kernel=True, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800153 copy_to_static_root=True, private_key=None,
Chris Sosa52148582012-11-15 15:35:58 -0800154 critical_update=False, remote_payload=False, max_updates= -1,
Chris Sosa6a3697f2013-01-29 16:44:43 -0800155 host_log=False, *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700156 super(Autoupdate, self).__init__(*args, **kwargs)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700157 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -0700158 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700159 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700160 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700161 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700162
Chris Sosa0356d3b2010-09-16 15:46:22 -0700163 self.forced_image = forced_image
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700164 self.payload_path = payload_path
Chris Sosa62f720b2010-10-26 21:39:48 -0700165 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800166 self.proxy_port = proxy_port
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700167 self.patch_kernel = patch_kernel
Chris Sosae67b78f2010-11-04 17:33:16 -0700168 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800169 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800170 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800171 self.critical_update = critical_update
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700172 self.remote_payload = remote_payload
Jay Srinivasanac69d262012-10-30 19:05:53 -0700173 self.max_updates = max_updates
Gilad Arnold8318eac2012-10-04 12:52:23 -0700174 self.host_log = host_log
Don Garrettfff4c322010-11-19 13:37:12 -0800175
Chris Sosa417e55d2011-01-25 16:40:48 -0800176 # Path to pre-generated file.
177 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700178
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700179 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800180 # information about a given host. A host is identified by its IP address.
181 # The info stored for each host includes a complete log of events for this
182 # host, as well as a dictionary of current attributes derived from events.
183 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700184
Chris Sosa6a3697f2013-01-29 16:44:43 -0800185 @classmethod
186 def _ReadMetadataFromStream(cls, stream):
187 """Returns metadata obj from input json stream that implements .read()."""
188 file_attr_dict = {}
189 try:
190 file_attr_dict = json.loads(stream.read())
191 except IOError:
192 return None
193
194 sha1 = file_attr_dict.get(cls.SHA1_ATTR)
195 sha256 = file_attr_dict.get(cls.SHA256_ATTR)
196 size = file_attr_dict.get(cls.SIZE_ATTR)
197 is_delta = file_attr_dict.get(cls.ISDELTA_ATTR)
198 return UpdateMetadata(sha1, sha256, size, is_delta)
199
200 @staticmethod
201 def _ReadMetadataFromFile(payload_dir):
202 """Returns metadata object from the metadata_file in the payload_dir"""
joychen25d25972013-07-30 14:54:16 -0700203 metadata_file = os.path.join(payload_dir, constants.METADATA_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800204 if os.path.exists(metadata_file):
205 with open(metadata_file, 'r') as metadata_stream:
206 return Autoupdate._ReadMetadataFromStream(metadata_stream)
207
208 @classmethod
209 def _StoreMetadataToFile(cls, payload_dir, metadata_obj):
210 """Stores metadata object into the metadata_file of the payload_dir"""
211 file_dict = {cls.SHA1_ATTR: metadata_obj.sha1,
212 cls.SHA256_ATTR: metadata_obj.sha256,
213 cls.SIZE_ATTR: metadata_obj.size,
214 cls.ISDELTA_ATTR: metadata_obj.is_delta_format}
joychen25d25972013-07-30 14:54:16 -0700215 metadata_file = os.path.join(payload_dir, constants.METADATA_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800216 with open(metadata_file, 'w') as file_handle:
217 json.dump(file_dict, file_handle)
218
Chris Sosa52148582012-11-15 15:35:58 -0800219 @staticmethod
220 def _GetVersionFromDir(image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700221 """Returns the version of the image based on the name of the directory."""
222 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700223 parts = latest_version.split('-')
224 if len(parts) == 2:
225 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
226 # TODO(derat): Remove the code for old-style versions after 20120101.
227 return parts[0]
228 else:
229 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
230 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700231
Chris Sosa52148582012-11-15 15:35:58 -0800232 @staticmethod
233 def _CanUpdate(client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800234 """Returns true if the latest_version is greater than the client_version.
235 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800236 _Log('client version %s latest version %s', client_version, latest_version)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700237
238 client_tokens = client_version.replace('_', '').split('.')
239 # If the client has an old four-token version like "0.16.892.0", drop the
240 # first two tokens -- we use versions like "892.0.0" now.
241 # TODO(derat): Remove the code for old-style versions after 20120101.
242 if len(client_tokens) == 4:
243 client_tokens = client_tokens[2:]
244
245 latest_tokens = latest_version.replace('_', '').split('.')
246 if len(latest_tokens) == 4:
247 latest_tokens = latest_tokens[2:]
248
249 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700250 if int(latest_tokens[i]) == int(client_tokens[i]):
251 continue
252 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700253
254 # Favor four-token new-style versions on the server over old-style versions
255 # on the client if everything else matches.
256 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700257
Chris Sosa0356d3b2010-09-16 15:46:22 -0700258 def _GetImageName(self):
259 """Returns the name of the image that should be used."""
260 if self.use_test_image:
261 image_name = 'chromiumos_test_image.bin'
262 else:
263 image_name = 'chromiumos_image.bin'
Chris Sosa6a3697f2013-01-29 16:44:43 -0800264
Chris Sosa0356d3b2010-09-16 15:46:22 -0700265 return image_name
266
Chris Sosa52148582012-11-15 15:35:58 -0800267 @staticmethod
Gilad Arnolde74b3812013-04-22 11:27:38 -0700268 def IsDeltaFormatFile(filename):
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700269 try:
Gilad Arnolde74b3812013-04-22 11:27:38 -0700270 with open(filename) as payload_file:
271 payload = update_payload.Payload(payload_file)
272 payload.Init()
273 return payload.IsDelta()
274 except (IOError, update_payload.PayloadError):
275 # For unit tests we may not have real files, so it's ok to ignore these
276 # errors.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700277 return False
278
Don Garrettf90edf02010-11-16 17:36:14 -0800279 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700280 """Generates an update gz given a full path to an image.
281
282 Args:
283 image_path: Full path to image.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800284 Raises:
285 subprocess.CalledProcessError if the update generator fails to generate a
286 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700287 """
joychen7c2054a2013-07-25 11:14:07 -0700288 update_path = os.path.join(output_dir, constants.UPDATE_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800289 _Log('Generating update image %s', update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700290
Chris Sosa0f1ec842011-02-14 16:33:22 -0800291 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700292 'cros_generate_update_payload',
Chris Sosa6a3697f2013-01-29 16:44:43 -0800293 '--image', image_path,
294 '--output', update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800295 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700296
Chris Sosa52148582012-11-15 15:35:58 -0800297 if src_image:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800298 update_command.extend(['--src_image', src_image])
Chris Sosa52148582012-11-15 15:35:58 -0800299
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700300 if self.patch_kernel:
Chris Sosa52148582012-11-15 15:35:58 -0800301 update_command.append('--patch_kernel')
302
303 if self.private_key:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800304 update_command.extend(['--private_key', self.private_key])
Chris Sosa0f1ec842011-02-14 16:33:22 -0800305
Chris Sosa6a3697f2013-01-29 16:44:43 -0800306 _Log('Running %s', ' '.join(update_command))
307 subprocess.check_call(update_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700308
Chris Sosa52148582012-11-15 15:35:58 -0800309 @staticmethod
310 def GenerateStatefulFile(image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800311 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700312
313 Args:
314 image_path: Full path to image.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800315 Raises:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800316 subprocess.CalledProcessError if the update generator fails to generate a
Chris Sosa908fd6f2010-11-10 17:31:18 -0800317 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700318 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800319 update_command = [
320 'cros_generate_stateful_update_payload',
321 '--image', image_path,
322 '--output_dir', output_dir,
323 ]
324 _Log('Running %s', ' '.join(update_command))
325 subprocess.check_call(update_command)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700326
Don Garrettf90edf02010-11-16 17:36:14 -0800327 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
328 """Find directory to store a cached update.
329
Gilad Arnold55a2a372012-10-02 09:46:32 -0700330 Given one, or two images for an update, this finds which cache directory
331 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-16 17:36:14 -0800332
Gilad Arnold55a2a372012-10-02 09:46:32 -0700333 Returns:
334 A directory path for storing a cached update, of the following form:
335 Non-delta updates:
336 CACHE_DIR/<dest_hash>
337 Delta updates:
338 CACHE_DIR/<src_hash>_<dest_hash>
339 Signed updates (self.private_key):
340 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-07 19:32:50 -0700341 """
Gilad Arnold55a2a372012-10-02 09:46:32 -0700342 update_dir = ''
Chris Sosa744e1472011-09-07 19:32:50 -0700343 if src_image:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700344 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-16 17:36:14 -0800345
Gilad Arnold55a2a372012-10-02 09:46:32 -0700346 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-07 19:32:50 -0700347 if self.private_key:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700348 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-07 19:32:50 -0700349
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700350 if self.patch_kernel:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700351 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 10:15:47 -0800352
joychen25d25972013-07-30 14:54:16 -0700353 return os.path.join(constants.CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800354
Don Garrettfff4c322010-11-19 13:37:12 -0800355 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800356 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700357
Chris Sosade91f672010-11-16 10:05:44 -0800358 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800359 src_image: image we are updating from (Null/empty for non-delta)
360 image_path: full path to the image.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800361 output_dir: the directory to write the update payloads to
362 Raises:
363 AutoupdateError if it failed to generate either update or stateful
364 payload.
Chris Sosade91f672010-11-16 10:05:44 -0800365 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800366 _Log('Generating update for image %s', image_path)
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700367
Chris Sosa6a3697f2013-01-29 16:44:43 -0800368 # Delete any previous state in this directory.
369 os.system('rm -rf "%s"' % output_dir)
370 os.makedirs(output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000371
Chris Sosa6a3697f2013-01-29 16:44:43 -0800372 try:
373 self.GenerateUpdateFile(self.src_image, image_path, output_dir)
374 self.GenerateStatefulFile(image_path, output_dir)
375 except subprocess.CalledProcessError:
376 os.system('rm -rf "%s"' % output_dir)
377 raise AutoupdateError('Failed to generate update in %s' % output_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800378
379 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
380 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000381
Chris Sosa0356d3b2010-09-16 15:46:22 -0700382 Args:
383 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700384 static_image_dir: the directory to move images to after generating.
385 Returns:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800386 update directory relative to static_image_dir. None if it should
387 serve from the static_image_dir.
388 Raises:
389 AutoupdateError if it we need to generate a payload and fail to do so.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700390 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800391 _Log('Generating update for src %s image %s', self.src_image, image_path)
Chris Sosae67b78f2010-11-04 17:33:16 -0700392
Chris Sosa417e55d2011-01-25 16:40:48 -0800393 # If it was pregenerated_path, don't regenerate
394 if self.pregenerated_path:
395 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800396
Don Garrettf90edf02010-11-16 17:36:14 -0800397 # Which sub_dir of static_image_dir should hold our cached update image
joychen25d25972013-07-30 14:54:16 -0700398 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image,
399 image_path)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800400 _Log('Caching in sub_dir "%s"', cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800401
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,
joychen7c2054a2013-07-25 11:14:07 -0700404 cache_sub_dir,
405 constants.UPDATE_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800406 cache_stateful_payload = os.path.join(static_image_dir,
joychen25d25972013-07-30 14:54:16 -0700407 cache_sub_dir,
408 constants.STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800409
Chris Sosa6a3697f2013-01-29 16:44:43 -0800410 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800411 # Check to see if this cache directory is valid.
412 if not os.path.exists(cache_update_payload) or not os.path.exists(
413 cache_stateful_payload):
Chris Sosa6a3697f2013-01-29 16:44:43 -0800414 self.GenerateUpdateImage(image_path, full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800415
Chris Sosa6a3697f2013-01-29 16:44:43 -0800416 self.pregenerated_path = cache_sub_dir
Chris Sosa65d339b2013-01-21 18:59:21 -0800417
Chris Sosa6a3697f2013-01-29 16:44:43 -0800418 # Generate the cache file.
419 self.GetLocalPayloadAttrs(full_cache_dir)
joychen25d25972013-07-30 14:54:16 -0700420 cache_metadata_file = os.path.join(full_cache_dir, constants.METADATA_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800421
Chris Sosa08d55a22011-01-19 16:08:02 -0800422 # Generation complete, copy if requested.
423 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800424 # The final results exist directly in static
joychen7c2054a2013-07-25 11:14:07 -0700425 cros_update_payload = os.path.join(static_image_dir,
426 constants.UPDATE_FILE)
joychen25d25972013-07-30 14:54:16 -0700427 stateful_payload = os.path.join(static_image_dir, constants.STATEFUL_FILE)
428 metadata_file = os.path.join(static_image_dir, constants.METADATA_FILE)
Gilad Arnolde74b3812013-04-22 11:27:38 -0700429 common_util.CopyFile(cache_update_payload, cros_update_payload)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700430 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800431 common_util.CopyFile(cache_metadata_file, metadata_file)
432 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800433 else:
434 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700435
Chris Sosa6a3697f2013-01-29 16:44:43 -0800436 def GenerateLatestUpdateImage(self, board, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800437 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700438 """Generates an update using the latest image that has been built.
439
440 This will only generate an update if the newest update is newer than that
441 on the client or client_version is 'ForcedUpdate'.
442
443 Args:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800444 board: Name of the board.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700445 client_version: Current version of the client or 'ForcedUpdate'
446 static_image_dir: the directory to move images to after generating.
447 Returns:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800448 Name of the update directory relative to the static dir. None if it should
449 serve from the static_image_dir.
450 Raises:
451 AutoupdateError if it failed to generate the payload or can't update
452 the given client_version.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700453 """
joychen921e1fb2013-06-28 11:12:20 -0700454 latest_image_dir = self.GetLatestImageDir(board)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700455 latest_version = self._GetVersionFromDir(latest_image_dir)
456 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
457
Chris Sosa0356d3b2010-09-16 15:46:22 -0700458 # Check to see whether or not we should update.
459 if client_version != 'ForcedUpdate' and not self._CanUpdate(
460 client_version, latest_version):
Chris Sosa6a3697f2013-01-29 16:44:43 -0800461 raise AutoupdateError('Update check received but no update available '
462 'for client')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700463
Don Garrettf90edf02010-11-16 17:36:14 -0800464 return self.GenerateUpdateImageWithCache(latest_image_path,
465 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700466
Chris Sosa6a3697f2013-01-29 16:44:43 -0800467 def GenerateUpdatePayload(self, board, client_version, static_image_dir):
468 """Generates an update for an image and returns the relative payload dir.
Chris Sosaa73ec162010-05-03 20:18:02 -0700469
Chris Sosa6a3697f2013-01-29 16:44:43 -0800470 Returns:
471 payload dir relative to static_image_dir. None if it should
472 serve from the static_image_dir.
473 Raises:
474 AutoupdateError if it failed to generate the payload.
Don Garrettf90edf02010-11-16 17:36:14 -0800475 """
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700476 if self.payload_path:
joychen7c2054a2013-07-25 11:14:07 -0700477 dest_path = os.path.join(static_image_dir, constants.UPDATE_FILE)
joychen25d25972013-07-30 14:54:16 -0700478 dest_stateful = os.path.join(static_image_dir, constants.STATEFUL_FILE)
joychen7c2054a2013-07-25 11:14:07 -0700479
Don Garrett0c880e22010-11-17 18:13:37 -0800480 # If the forced payload is not already in our static_image_dir,
481 # copy it there.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700482 src_path = os.path.abspath(self.payload_path)
joychen25d25972013-07-30 14:54:16 -0700483 src_stateful = os.path.join(os.path.dirname(src_path),
484 constants.STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800485 # Only copy the files if the source directory is different from dest.
486 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700487 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 12:09:35 -0800488
489 # The stateful payload is optional.
490 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700491 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 12:09:35 -0800492 else:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800493 _Log('WARN: %s not found. Expected for dev and test builds',
joychen25d25972013-07-30 14:54:16 -0700494 constants.STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800495 if os.path.exists(dest_stateful):
496 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800497
Chris Sosa6a3697f2013-01-29 16:44:43 -0800498 # Serve from the main directory so rel_path is None.
499 return None
Don Garrett0c880e22010-11-17 18:13:37 -0800500 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800501 return self.GenerateUpdateImageWithCache(
502 self.forced_image,
503 static_image_dir=static_image_dir)
Chris Sosa65d339b2013-01-21 18:59:21 -0800504 else:
joychen7c2054a2013-07-25 11:14:07 -0700505 update_path = os.path.join(static_image_dir, constants.UPDATE_FILE)
506 # if a label was specified, check if the update file is there.
507 if static_image_dir != self.static_dir and os.path.exists(update_path):
508 return None
509
Chris Sosa6a3697f2013-01-29 16:44:43 -0800510 if not board:
511 raise AutoupdateError(
512 'Failed to generate update. '
513 'You must set --board when pre-generating latest update.')
Chris Sosa65d339b2013-01-21 18:59:21 -0800514
Chris Sosa6a3697f2013-01-29 16:44:43 -0800515 return self.GenerateLatestUpdateImage(board, client_version,
516 static_image_dir)
Chris Sosa2c048f12010-10-27 16:05:27 -0700517
518 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800519 """Pre-generates an update and prints out the relative path it.
520
Chris Sosa6a3697f2013-01-29 16:44:43 -0800521 Returns relative path of the update.
Chris Sosa65d339b2013-01-21 18:59:21 -0800522
Chris Sosa6a3697f2013-01-29 16:44:43 -0800523 Raises:
524 AutoupdateError if it failed to generate the payload.
525 """
526 _Log('Pre-generating the update payload')
527 # Does not work with labels so just use static dir.
528 pregenerated_update = self.GenerateUpdatePayload(self.board, '0.0.0.0',
529 self.static_dir)
530 print 'PREGENERATED_UPDATE=%s' % _NonePathJoin(pregenerated_update,
joychen7c2054a2013-07-25 11:14:07 -0700531 constants.UPDATE_FILE)
Chris Sosa417e55d2011-01-25 16:40:48 -0800532 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700533
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700534 def _GetRemotePayloadAttrs(self, url):
535 """Returns hashes, size and delta flag of a remote update payload.
536
537 Obtain attributes of a payload file available on a remote devserver. This
538 is based on the assumption that the payload URL uses the /static prefix. We
539 need to make sure that both clients (requests) and remote devserver
540 (provisioning) preserve this invariant.
541
542 Args:
543 url: URL of statically staged remote file (http://host:port/static/...)
544 Returns:
545 A tuple containing the SHA1, SHA256, file size and whether or not it's a
546 delta payload (Boolean).
547 """
548 if self._PAYLOAD_URL_PREFIX not in url:
549 raise AutoupdateError(
550 'Payload URL does not have the expected prefix (%s)' %
551 self._PAYLOAD_URL_PREFIX)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800552
joychened64b222013-06-21 16:39:34 -0700553 if self._OLD_PAYLOAD_URL_PREFIX in url:
554 fileinfo_url = url.replace(self._OLD_PAYLOAD_URL_PREFIX,
555 self._FILEINFO_URL_PREFIX)
556 else:
557 fileinfo_url = url.replace(self._PAYLOAD_URL_PREFIX,
558 self._FILEINFO_URL_PREFIX)
559
Chris Sosa6a3697f2013-01-29 16:44:43 -0800560 _Log('Retrieving file info for remote payload via %s', fileinfo_url)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700561 try:
562 conn = urllib2.urlopen(fileinfo_url)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800563 metadata_obj = Autoupdate._ReadMetadataFromStream(conn)
564 # These fields are required for remote calls.
565 if not metadata_obj:
566 raise AutoupdateError('Failed to obtain remote payload info')
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700567
Chris Sosa6a3697f2013-01-29 16:44:43 -0800568 return metadata_obj
569 except IOError as e:
570 raise AutoupdateError('Failed to obtain remote payload info: %s', e)
571
572 def GetLocalPayloadAttrs(self, payload_dir):
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700573 """Returns hashes, size and delta flag of a local update payload.
574
575 Args:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800576 payload_dir: Path to the directory the payload is in.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700577 Returns:
578 A tuple containing the SHA1, SHA256, file size and whether or not it's a
579 delta payload (Boolean).
580 """
joychen7c2054a2013-07-25 11:14:07 -0700581 filename = os.path.join(payload_dir, constants.UPDATE_FILE)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800582 if not os.path.exists(filename):
583 raise AutoupdateError('update.gz not present in payload dir %s' %
584 payload_dir)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700585
Chris Sosa6a3697f2013-01-29 16:44:43 -0800586 metadata_obj = Autoupdate._ReadMetadataFromFile(payload_dir)
587 if not metadata_obj or not (metadata_obj.sha1 and
588 metadata_obj.sha256 and
589 metadata_obj.size):
590 sha1 = common_util.GetFileSha1(filename)
591 sha256 = common_util.GetFileSha256(filename)
592 size = common_util.GetFileSize(filename)
Gilad Arnolde74b3812013-04-22 11:27:38 -0700593 is_delta_format = self.IsDeltaFormatFile(filename)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800594 metadata_obj = UpdateMetadata(sha1, sha256, size, is_delta_format)
595 Autoupdate._StoreMetadataToFile(payload_dir, metadata_obj)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700596
Chris Sosa6a3697f2013-01-29 16:44:43 -0800597 return metadata_obj
598
599 def _ProcessUpdateComponents(self, app, event):
600 """Processes the app and event components of an update request.
601
602 Returns tuple containing forced_update_label, client_version, and board.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700603 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800604 # Initialize an empty dictionary for event attributes to log.
605 log_message = {}
Jay Srinivasanac69d262012-10-30 19:05:53 -0700606
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700607 # Determine request IP, strip any IPv6 data for simplicity.
608 client_ip = cherrypy.request.remote.ip.split(':')[-1]
Gilad Arnold286a0062012-01-12 13:47:02 -0800609 # Obtain (or init) info object for this client.
610 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
611
Chris Sosa6a3697f2013-01-29 16:44:43 -0800612 client_version = 'ForcedUpdate'
613 board = None
614 if app:
615 client_version = app.getAttribute('version')
616 channel = app.getAttribute('track')
617 board = (app.hasAttribute('board') and app.getAttribute('board')
joychenb0dfe552013-07-30 10:02:06 -0700618 or self.GetDefaultBoardID())
Chris Sosa6a3697f2013-01-29 16:44:43 -0800619 # Add attributes to log message
620 log_message['version'] = client_version
621 log_message['track'] = channel
622 log_message['board'] = board
623 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700624
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700625 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800626 event_result = int(event[0].getAttribute('eventresult'))
627 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700628 client_previous_version = (event[0].getAttribute('previousversion')
629 if event[0].hasAttribute('previousversion')
630 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800631 # Store attributes to legacy host info structure
632 curr_host_info.attrs['last_event_status'] = event_result
633 curr_host_info.attrs['last_event_type'] = event_type
634 # Add attributes to log message
635 log_message['event_result'] = event_result
636 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700637 if client_previous_version is not None:
638 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800639
Gilad Arnold8318eac2012-10-04 12:52:23 -0700640 # Log host event, if so instructed.
641 if self.host_log:
642 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700643
Chris Sosa6a3697f2013-01-29 16:44:43 -0800644 return (curr_host_info.attrs.pop('forced_update_label', None),
645 client_version, board)
646
647 def _GetStaticUrl(self):
648 """Returns the static url base that should prefix all payload responses."""
649 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
650 if x_forwarded_host:
651 hostname = 'http://' + x_forwarded_host
652 else:
653 hostname = cherrypy.request.base
654
655 if self.urlbase:
656 static_urlbase = self.urlbase
Chris Sosa6a3697f2013-01-29 16:44:43 -0800657 else:
658 static_urlbase = '%s/static' % hostname
659
660 # If we have a proxy port, adjust the URL we instruct the client to
661 # use to go through the proxy.
662 if self.proxy_port:
663 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
664
665 _Log('Using static url base %s', static_urlbase)
666 _Log('Handling update ping as %s', hostname)
667 return static_urlbase
668
669 def HandleUpdatePing(self, data, label=None):
670 """Handles an update ping from an update client.
671
672 Args:
673 data: XML blob from client.
674 label: optional label for the update.
675 Returns:
676 Update payload message for client.
677 """
678 # Get the static url base that will form that base of our update url e.g.
679 # http://hostname:8080/static/update.gz.
680 static_urlbase = self._GetStaticUrl()
681
682 # Parse the XML we got into the components we care about.
683 protocol, app, event, update_check = autoupdate_lib.ParseUpdateRequest(data)
684
685 # #########################################################################
686 # Process attributes of the update check.
687 forced_update_label, client_version, board = self._ProcessUpdateComponents(
688 app, event)
689
690 # We only process update_checks in the update rpc.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700691 if not update_check:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800692 _Log('Non-update check received. Returning blank payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700693 # TODO(sosa): Generate correct non-updatecheck payload to better test
694 # update clients.
Chris Sosa52148582012-11-15 15:35:58 -0800695 return autoupdate_lib.GetNoUpdateResponse(protocol)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700696
Chris Sosa6a3697f2013-01-29 16:44:43 -0800697 # In case max_updates is used, return no response if max reached.
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700698 if self.max_updates > 0:
699 self.max_updates -= 1
700 elif self.max_updates == 0:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800701 _Log('Request received but max number of updates handled')
Chris Sosa52148582012-11-15 15:35:58 -0800702 return autoupdate_lib.GetNoUpdateResponse(protocol)
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700703
Chris Sosa6a3697f2013-01-29 16:44:43 -0800704 _Log('Update Check Received. Client is using protocol version: %s',
705 protocol)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700706
Chris Sosa6a3697f2013-01-29 16:44:43 -0800707 if forced_update_label:
708 if label:
709 _Log('Label: %s set but being overwritten to %s by request', label,
710 forced_update_label)
711
712 label = forced_update_label
713
714 # #########################################################################
715 # Finally its time to generate the omaha response to give to client that
716 # lets them know where to find the payload and its associated metadata.
717 metadata_obj = None
718
719 try:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700720 # Are we provisioning a remote or local payload?
721 if self.remote_payload:
722 # If no explicit label was provided, use the value of --payload.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800723 if not label:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700724 label = self.payload_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700725
Chris Sosa52f15bc2013-08-13 17:14:15 -0700726 # TODO(sosa): Remove backwards-compatible hack.
727 if not label.endswith('.bin'):
728 url = _NonePathJoin(static_urlbase, label, 'update.gz')
729 else:
730 url = _NonePathJoin(static_urlbase, label)
Chris Sosa5d342a22010-09-28 16:54:41 -0700731
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700732 # Get remote payload attributes.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800733 metadata_obj = self._GetRemotePayloadAttrs(url)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700734 else:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800735 static_image_dir = _NonePathJoin(self.static_dir, label)
736 rel_path = None
joychen7c2054a2013-07-25 11:14:07 -0700737 url = _NonePathJoin(static_urlbase, label, rel_path,
738 constants.UPDATE_FILE)
Chris Sosa9d301582013-08-06 13:35:44 -0700739 # Local path to the update file.
740 static_file_path = _NonePathJoin(static_image_dir,
741 constants.UPDATE_FILE)
joychen7c2054a2013-07-25 11:14:07 -0700742 if common_util.IsInsideChroot():
Chris Sosa6a3697f2013-01-29 16:44:43 -0800743 rel_path = self.GenerateUpdatePayload(board, client_version,
744 static_image_dir)
joychen7c2054a2013-07-25 11:14:07 -0700745 url = _NonePathJoin(static_urlbase, label, rel_path,
746 constants.UPDATE_FILE)
Chris Sosa9d301582013-08-06 13:35:44 -0700747 elif not os.path.exists(static_file_path):
joychen7c2054a2013-07-25 11:14:07 -0700748 # the update payload wasn't found. This update can't happen.
749 raise AutoupdateError("Failed to find an update payload at %s", url)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800750
Chris Sosa6a3697f2013-01-29 16:44:43 -0800751 local_payload_dir = _NonePathJoin(static_image_dir, rel_path)
752 metadata_obj = self.GetLocalPayloadAttrs(local_payload_dir)
753
754 except AutoupdateError as e:
755 # Raised if we fail to generate an update payload.
756 _Log('Failed to process an update: %r', e)
757 return autoupdate_lib.GetNoUpdateResponse(protocol)
758
759 _Log('Responding to client to use url %s to get image', url)
760 return autoupdate_lib.GetUpdateResponse(
761 metadata_obj.sha1, metadata_obj.sha256, metadata_obj.size, url,
762 metadata_obj.is_delta_format, protocol, self.critical_update)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700763
764 def HandleHostInfoPing(self, ip):
765 """Returns host info dictionary for the given IP in JSON format."""
766 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800767 if ip in self.host_infos.table:
768 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
769
770 def HandleHostLogPing(self, ip):
771 """Returns a complete log of events for host in JSON format."""
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700772 # If all events requested, return a dictionary of logs keyed by IP address.
Gilad Arnold286a0062012-01-12 13:47:02 -0800773 if ip == 'all':
774 return json.dumps(
775 dict([(key, self.host_infos.table[key].log)
776 for key in self.host_infos.table]))
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700777
778 # Otherwise we're looking for a specific IP address, so find its log.
Gilad Arnold286a0062012-01-12 13:47:02 -0800779 if ip in self.host_infos.table:
780 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700781
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700782 # If no events were logged for this IP, return an empty log.
783 return json.dumps([])
784
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700785 def HandleSetUpdatePing(self, ip, label):
786 """Sets forced_update_label for a given host."""
787 assert ip, 'No ip provided.'
788 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800789 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label