blob: f11820bf11d4aa21c4017b29ec84c4fc03a015f6 [file] [log] [blame]
Amin Hassani8d718d12019-06-02 21:28:39 -07001# -*- coding: utf-8 -*-
Darin Petkovc3fd90c2011-05-11 14:23:00 -07002# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Gilad Arnoldd8d595c2014-03-21 13:00:41 -07006"""Devserver module for handling update client requests."""
7
Don Garrettfb15e322016-06-21 19:12:08 -07008from __future__ import print_function
9
Gilad Arnolde7819e72014-03-21 12:50:48 -070010import collections
Dale Curtisc9aaf3a2011-08-09 15:47:40 -070011import json
rtc@google.comded22402009-10-26 22:36:21 +000012import os
Gilad Arnoldd0c71752013-12-06 11:48:45 -080013import threading
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070014import time
Don Garrett0ad09372010-12-06 16:20:30 -080015import urlparse
Chris Sosa7c931362010-10-11 19:49:01 -070016
Gilad Arnoldabb352e2012-09-23 01:24:27 -070017import cherrypy
18
joychen921e1fb2013-06-28 11:12:20 -070019import build_util
Gilad Arnold55a2a372012-10-02 09:46:32 -070020import common_util
joychen7c2054a2013-07-25 11:14:07 -070021import devserver_constants as constants
Gilad Arnoldc65330c2012-09-20 15:17:48 -070022import log_util
Amin Hassani51780c62017-08-10 13:55:35 -070023
Amin Hassani8d718d12019-06-02 21:28:39 -070024# TODO(crbug.com/872441): We try to import nebraska from different places
25# because when we install the devserver, we copy the nebraska.py into the main
26# directory. Once this bug is resolved, we can always import from nebraska
27# directory.
28try:
29 from nebraska import nebraska
30except ImportError:
31 import nebraska
Chris Sosa05491b12010-11-08 17:14:16 -080032
Gilad Arnoldc65330c2012-09-20 15:17:48 -070033
34# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080035def _Log(message, *args):
36 return log_util.LogWithTag('UPDATE', message, *args)
Gilad Arnoldc65330c2012-09-20 15:17:48 -070037
rtc@google.comded22402009-10-26 22:36:21 +000038
Gilad Arnold0c9c8602012-10-02 23:58:58 -070039class AutoupdateError(Exception):
40 """Exception classes used by this module."""
41 pass
42
43
Don Garrett0ad09372010-12-06 16:20:30 -080044def _ChangeUrlPort(url, new_port):
45 """Return the URL passed in with a different port"""
46 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
47 host_port = netloc.split(':')
48
49 if len(host_port) == 1:
50 host_port.append(new_port)
51 else:
52 host_port[1] = new_port
53
Don Garrettfb15e322016-06-21 19:12:08 -070054 print(host_port)
joychen121fc9b2013-08-02 14:30:30 -070055 netloc = '%s:%s' % tuple(host_port)
Don Garrett0ad09372010-12-06 16:20:30 -080056
57 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
58
Chris Sosa6a3697f2013-01-29 16:44:43 -080059def _NonePathJoin(*args):
60 """os.path.join that filters None's from the argument list."""
61 return os.path.join(*filter(None, args))
Don Garrett0ad09372010-12-06 16:20:30 -080062
Chris Sosa6a3697f2013-01-29 16:44:43 -080063
64class HostInfo(object):
Gilad Arnold286a0062012-01-12 13:47:02 -080065 """Records information about an individual host.
66
67 Members:
68 attrs: Static attributes (legacy)
69 log: Complete log of recorded client entries
70 """
71
72 def __init__(self):
73 # A dictionary of current attributes pertaining to the host.
74 self.attrs = {}
75
76 # A list of pairs consisting of a timestamp and a dictionary of recorded
77 # attributes.
78 self.log = []
79
80 def __repr__(self):
81 return 'attrs=%s, log=%s' % (self.attrs, self.log)
82
83 def AddLogEntry(self, entry):
84 """Append a new log entry."""
85 # Append a timestamp.
86 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
87 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
88 # Add entry to hosts' message log.
89 self.log.append(entry)
90
Gilad Arnold286a0062012-01-12 13:47:02 -080091
Chris Sosa6a3697f2013-01-29 16:44:43 -080092class HostInfoTable(object):
Gilad Arnold286a0062012-01-12 13:47:02 -080093 """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."""
Chris Sosa1885d032012-11-29 17:07:27 -0800112 return self.table.get(host_id)
Gilad Arnold286a0062012-01-12 13:47:02 -0800113
114
joychen921e1fb2013-06-28 11:12:20 -0700115class Autoupdate(build_util.BuildObject):
Amin Hassanie9ffb862019-09-25 17:10:40 -0700116 """Class that contains functionality that handles Chrome OS update pings."""
rtc@google.comded22402009-10-26 22:36:21 +0000117
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700118 _PAYLOAD_URL_PREFIX = '/static/'
Chris Sosa6a3697f2013-01-29 16:44:43 -0800119
Amin Hassanie9ffb862019-09-25 17:10:40 -0700120 def __init__(self, xbuddy, payload_path=None, proxy_port=None,
Amin Hassanic9dd11e2019-07-11 15:33:55 -0700121 critical_update=False, max_updates=-1, host_log=False,
122 *args, **kwargs):
Amin Hassanie9ffb862019-09-25 17:10:40 -0700123 """Initializes the class.
124
125 Args:
126 xbuddy: The xbuddy path.
127 payload_path: The path to pre-generated payload to serve.
128 proxy_port: The port of local proxy to tell client to connect to you
129 through.
130 critical_update: Whether provisioned payload is critical.
131 max_updates: The maximum number of updates we'll try to provision.
132 host_log: Record full history of host update events.
133 """
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700134 super(Autoupdate, self).__init__(*args, **kwargs)
joychen121fc9b2013-08-02 14:30:30 -0700135 self.xbuddy = xbuddy
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700136 self.payload_path = payload_path
Don Garrett0ad09372010-12-06 16:20:30 -0800137 self.proxy_port = proxy_port
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800138 self.critical_update = critical_update
Jay Srinivasanac69d262012-10-30 19:05:53 -0700139 self.max_updates = max_updates
Gilad Arnold8318eac2012-10-04 12:52:23 -0700140 self.host_log = host_log
Don Garrettfff4c322010-11-19 13:37:12 -0800141
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700142 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800143 # information about a given host. A host is identified by its IP address.
144 # The info stored for each host includes a complete log of events for this
145 # host, as well as a dictionary of current attributes derived from events.
146 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700147
Gilad Arnolde7819e72014-03-21 12:50:48 -0700148 self._update_count_lock = threading.Lock()
Gilad Arnoldd0c71752013-12-06 11:48:45 -0800149
Amin Hassanie9ffb862019-09-25 17:10:40 -0700150 def GetUpdateForLabel(self, label):
joychen121fc9b2013-08-02 14:30:30 -0700151 """Given a label, get an update from the directory.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700152
joychen121fc9b2013-08-02 14:30:30 -0700153 Args:
joychen121fc9b2013-08-02 14:30:30 -0700154 label: the relative directory inside the static dir
Gilad Arnoldd8d595c2014-03-21 13:00:41 -0700155
Chris Sosa6a3697f2013-01-29 16:44:43 -0800156 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700157 A relative path to the directory with the update payload.
158 This is the label if an update did not need to be generated, but can
159 be label/cache/hashed_dir_for_update.
Gilad Arnoldd8d595c2014-03-21 13:00:41 -0700160
Chris Sosa6a3697f2013-01-29 16:44:43 -0800161 Raises:
joychen121fc9b2013-08-02 14:30:30 -0700162 AutoupdateError: If client version is higher than available update found
163 at the directory given by the label.
Don Garrettf90edf02010-11-16 17:36:14 -0800164 """
Amin Hassanie9ffb862019-09-25 17:10:40 -0700165 _Log('Update label: %s', label)
166 static_update_path = _NonePathJoin(self.static_dir, label,
167 constants.UPDATE_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800168
joychen121fc9b2013-08-02 14:30:30 -0700169 if label and os.path.exists(static_update_path):
170 # An update payload was found for the given label, return it.
171 return label
Don Garrett0c880e22010-11-17 18:13:37 -0800172
joychen121fc9b2013-08-02 14:30:30 -0700173 # The label didn't resolve.
Amin Hassanie9ffb862019-09-25 17:10:40 -0700174 _Log('Did not found any update payload for label %s.', label)
joychen121fc9b2013-08-02 14:30:30 -0700175 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700176
Amin Hassani8d718d12019-06-02 21:28:39 -0700177 def _ProcessUpdateComponents(self, request):
Gilad Arnolde7819e72014-03-21 12:50:48 -0700178 """Processes the components of an update request.
Chris Sosa6a3697f2013-01-29 16:44:43 -0800179
Gilad Arnolde7819e72014-03-21 12:50:48 -0700180 Args:
Amin Hassani8d718d12019-06-02 21:28:39 -0700181 request: A nebraska.Request object representing the update request.
Gilad Arnolde7819e72014-03-21 12:50:48 -0700182
183 Returns:
184 A named tuple containing attributes of the update requests as the
Amin Hassanie9ffb862019-09-25 17:10:40 -0700185 following fields: 'forced_update_label', 'board',
Gilad Arnolde7819e72014-03-21 12:50:48 -0700186 'event_result' and 'event_type'.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700187 """
Chris Sosa6a3697f2013-01-29 16:44:43 -0800188 # Initialize an empty dictionary for event attributes to log.
189 log_message = {}
Jay Srinivasanac69d262012-10-30 19:05:53 -0700190
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700191 # Determine request IP, strip any IPv6 data for simplicity.
192 client_ip = cherrypy.request.remote.ip.split(':')[-1]
Gilad Arnold286a0062012-01-12 13:47:02 -0800193 # Obtain (or init) info object for this client.
194 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
195
Amin Hassanie9ffb862019-09-25 17:10:40 -0700196 client_version = 'ForcedUpdate'
Chris Sosa6a3697f2013-01-29 16:44:43 -0800197 board = None
Amin Hassani8d718d12019-06-02 21:28:39 -0700198 event_result = None
199 event_type = None
200 if request.request_type != nebraska.Request.RequestType.EVENT:
201 client_version = request.version
202 channel = request.track
203 board = request.board or self.GetDefaultBoardID()
Chris Sosa6a3697f2013-01-29 16:44:43 -0800204 # Add attributes to log message
205 log_message['version'] = client_version
206 log_message['track'] = channel
207 log_message['board'] = board
208 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700209
Amin Hassani8d718d12019-06-02 21:28:39 -0700210 else:
211 event_result = request.app_requests[0].event_result
212 event_type = request.app_requests[0].event_type
213 client_previous_version = request.app_requests[0].previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800214 # Store attributes to legacy host info structure
215 curr_host_info.attrs['last_event_status'] = event_result
216 curr_host_info.attrs['last_event_type'] = event_type
217 # Add attributes to log message
218 log_message['event_result'] = event_result
219 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700220 if client_previous_version is not None:
221 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800222
Gilad Arnold8318eac2012-10-04 12:52:23 -0700223 # Log host event, if so instructed.
224 if self.host_log:
225 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700226
Gilad Arnolde7819e72014-03-21 12:50:48 -0700227 UpdateRequestAttrs = collections.namedtuple(
228 'UpdateRequestAttrs',
229 ('forced_update_label', 'client_version', 'board', 'event_result',
230 'event_type'))
231
232 return UpdateRequestAttrs(
233 curr_host_info.attrs.pop('forced_update_label', None),
234 client_version, board, event_result, event_type)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800235
David Rileyee75de22017-11-02 10:48:15 -0700236 def GetDevserverUrl(self):
237 """Returns the devserver url base."""
Chris Sosa6a3697f2013-01-29 16:44:43 -0800238 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
239 if x_forwarded_host:
240 hostname = 'http://' + x_forwarded_host
241 else:
242 hostname = cherrypy.request.base
243
David Rileyee75de22017-11-02 10:48:15 -0700244 return hostname
245
246 def GetStaticUrl(self):
247 """Returns the static url base that should prefix all payload responses."""
248 hostname = self.GetDevserverUrl()
249
Amin Hassanic9dd11e2019-07-11 15:33:55 -0700250 static_urlbase = '%s/static' % hostname
Chris Sosa6a3697f2013-01-29 16:44:43 -0800251 # If we have a proxy port, adjust the URL we instruct the client to
252 # use to go through the proxy.
253 if self.proxy_port:
254 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
255
256 _Log('Using static url base %s', static_urlbase)
257 _Log('Handling update ping as %s', hostname)
258 return static_urlbase
259
Amin Hassanie9ffb862019-09-25 17:10:40 -0700260 def GetPathToPayload(self, label, board):
joychen121fc9b2013-08-02 14:30:30 -0700261 """Find a payload locally.
262
263 See devserver's update rpc for documentation.
264
265 Args:
266 label: from update request
joychen121fc9b2013-08-02 14:30:30 -0700267 board: from update request
Gilad Arnoldd8d595c2014-03-21 13:00:41 -0700268
269 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700270 The relative path to an update from the static_dir
Gilad Arnoldd8d595c2014-03-21 13:00:41 -0700271
joychen121fc9b2013-08-02 14:30:30 -0700272 Raises:
273 AutoupdateError: If the update could not be found.
274 """
275 path_to_payload = None
Amin Hassanie9ffb862019-09-25 17:10:40 -0700276 # TODO(crbug.com/1006305): deprecate --payload flag
joychen121fc9b2013-08-02 14:30:30 -0700277 if self.payload_path:
278 # Copy the image from the path to '/forced_payload'
279 label = 'forced_payload'
280 dest_path = os.path.join(self.static_dir, label, constants.UPDATE_FILE)
281 dest_stateful = os.path.join(self.static_dir, label,
282 constants.STATEFUL_FILE)
Amin Hassani8d718d12019-06-02 21:28:39 -0700283 dest_meta = os.path.join(self.static_dir, label,
284 constants.UPDATE_METADATA_FILE)
joychen121fc9b2013-08-02 14:30:30 -0700285
286 src_path = os.path.abspath(self.payload_path)
Amin Hassanie9ffb862019-09-25 17:10:40 -0700287 src_meta = os.path.abspath(self.payload_path + '.json')
joychen121fc9b2013-08-02 14:30:30 -0700288 src_stateful = os.path.join(os.path.dirname(src_path),
289 constants.STATEFUL_FILE)
290 common_util.MkDirP(os.path.join(self.static_dir, label))
Alex Deymo3e2d4952013-09-03 21:49:41 -0700291 common_util.SymlinkFile(src_path, dest_path)
Amin Hassanie9ffb862019-09-25 17:10:40 -0700292 common_util.SymlinkFile(src_meta, dest_meta)
joychen121fc9b2013-08-02 14:30:30 -0700293 if os.path.exists(src_stateful):
294 # The stateful payload is optional.
Alex Deymo3e2d4952013-09-03 21:49:41 -0700295 common_util.SymlinkFile(src_stateful, dest_stateful)
joychen121fc9b2013-08-02 14:30:30 -0700296 else:
297 _Log('WARN: %s not found. Expected for dev and test builds',
298 constants.STATEFUL_FILE)
299 if os.path.exists(dest_stateful):
300 os.remove(dest_stateful)
Amin Hassanie9ffb862019-09-25 17:10:40 -0700301 path_to_payload = self.GetUpdateForLabel(label)
joychen121fc9b2013-08-02 14:30:30 -0700302 else:
303 label = label or ''
304 label_list = label.split('/')
305 # Suppose that the path follows old protocol of indexing straight
306 # into static_dir with board/version label.
307 # Attempt to get the update in that directory, generating if necc.
Amin Hassanie9ffb862019-09-25 17:10:40 -0700308 path_to_payload = self.GetUpdateForLabel(label)
joychen121fc9b2013-08-02 14:30:30 -0700309 if path_to_payload is None:
Amin Hassanie9ffb862019-09-25 17:10:40 -0700310 # There was no update found in the directory. Let XBuddy find the
311 # payloads.
joychen121fc9b2013-08-02 14:30:30 -0700312 if label_list[0] == 'xbuddy':
313 # If path explicitly calls xbuddy, pop off the tag.
314 label_list.pop()
Amin Hassanie9ffb862019-09-25 17:10:40 -0700315 x_label, _ = self.xbuddy.Translate(label_list, board=board)
316 # Path has been resolved, try to get the payload.
317 path_to_payload = self.GetUpdateForLabel(x_label)
joychen121fc9b2013-08-02 14:30:30 -0700318 if path_to_payload is None:
Amin Hassanie9ffb862019-09-25 17:10:40 -0700319 # No update payload found after translation. Try to get an update to
320 # a test image from GS using the label.
joychen121fc9b2013-08-02 14:30:30 -0700321 path_to_payload, _image_name = self.xbuddy.Get(
322 ['remote', label, 'full_payload'])
323
324 # One of the above options should have gotten us a relative path.
325 if path_to_payload is None:
326 raise AutoupdateError('Failed to get an update for: %s' % label)
Amin Hassani8d718d12019-06-02 21:28:39 -0700327
328 return path_to_payload
joychen121fc9b2013-08-02 14:30:30 -0700329
330 def HandleUpdatePing(self, data, label=''):
Chris Sosa6a3697f2013-01-29 16:44:43 -0800331 """Handles an update ping from an update client.
332
333 Args:
334 data: XML blob from client.
335 label: optional label for the update.
Gilad Arnoldd8d595c2014-03-21 13:00:41 -0700336
Chris Sosa6a3697f2013-01-29 16:44:43 -0800337 Returns:
338 Update payload message for client.
339 """
340 # Get the static url base that will form that base of our update url e.g.
341 # http://hostname:8080/static/update.gz.
David Rileyee75de22017-11-02 10:48:15 -0700342 static_urlbase = self.GetStaticUrl()
Chris Sosa6a3697f2013-01-29 16:44:43 -0800343
Chris Sosab26b1202013-08-16 16:40:55 -0700344 # Process attributes of the update check.
Amin Hassani8d718d12019-06-02 21:28:39 -0700345 request = nebraska.Request(data)
346 request_attrs = self._ProcessUpdateComponents(request)
Chris Sosab26b1202013-08-16 16:40:55 -0700347
Amin Hassani8d718d12019-06-02 21:28:39 -0700348 if request.request_type == nebraska.Request.RequestType.EVENT:
Gilad Arnolde7819e72014-03-21 12:50:48 -0700349 if ((request_attrs.event_type ==
Amin Hassani8d718d12019-06-02 21:28:39 -0700350 nebraska.Request.EVENT_TYPE_UPDATE_DOWNLOAD_STARTED) and
351 request_attrs.event_result == nebraska.Request.EVENT_RESULT_SUCCESS):
Gilad Arnolde7819e72014-03-21 12:50:48 -0700352 with self._update_count_lock:
353 if self.max_updates == 0:
354 _Log('Received too many download_started notifications. This '
355 'probably means a bug in the test environment, such as too '
356 'many clients running concurrently. Alternatively, it could '
357 'be a bug in the update client.')
358 elif self.max_updates > 0:
359 self.max_updates -= 1
joychen121fc9b2013-08-02 14:30:30 -0700360
Gilad Arnolde7819e72014-03-21 12:50:48 -0700361 _Log('A non-update event notification received. Returning an ack.')
Amin Hassani8d718d12019-06-02 21:28:39 -0700362 nebraska_obj = nebraska.Nebraska()
363 return nebraska_obj.GetResponseToRequest(request)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800364
Gilad Arnolde7819e72014-03-21 12:50:48 -0700365 # Make sure that we did not already exceed the max number of allowed update
366 # responses. Note that the counter is only decremented when the client
367 # reports an actual download, to avoid race conditions between concurrent
368 # update requests from the same client due to a timeout.
joychen121fc9b2013-08-02 14:30:30 -0700369 if self.max_updates == 0:
Gilad Arnolde7819e72014-03-21 12:50:48 -0700370 _Log('Request received but max number of updates already served.')
Amin Hassani8d718d12019-06-02 21:28:39 -0700371 nebraska_obj = nebraska.Nebraska()
372 return nebraska_obj.GetResponseToRequest(request, no_update=True)
joychen121fc9b2013-08-02 14:30:30 -0700373
Amin Hassani8d718d12019-06-02 21:28:39 -0700374 if request_attrs.forced_update_label:
375 if label:
376 _Log('Label: %s set but being overwritten to %s by request', label,
377 request_attrs.forced_update_label)
378 label = request_attrs.forced_update_label
joychen121fc9b2013-08-02 14:30:30 -0700379
Amin Hassani8d718d12019-06-02 21:28:39 -0700380 _Log('Update Check Received.')
Chris Sosa6a3697f2013-01-29 16:44:43 -0800381
382 try:
Amin Hassanie9ffb862019-09-25 17:10:40 -0700383 path_to_payload = self.GetPathToPayload(label, request_attrs.board)
Amin Hassani8d718d12019-06-02 21:28:39 -0700384 base_url = _NonePathJoin(static_urlbase, path_to_payload)
Amin Hassanic9dd11e2019-07-11 15:33:55 -0700385 local_payload_dir = _NonePathJoin(self.static_dir, path_to_payload)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800386 except AutoupdateError as e:
387 # Raised if we fail to generate an update payload.
Amin Hassani8d718d12019-06-02 21:28:39 -0700388 _Log('Failed to process an update request, but we will defer to '
389 'nebraska to respond with no-update. The error was %s', e)
Chris Sosa6a3697f2013-01-29 16:44:43 -0800390
Amin Hassani8d718d12019-06-02 21:28:39 -0700391 _Log('Responding to client to use url %s to get image', base_url)
392 nebraska_obj = nebraska.Nebraska(update_payloads_address=base_url,
393 update_metadata_dir=local_payload_dir)
394 return nebraska_obj.GetResponseToRequest(
395 request, critical_update=self.critical_update)
Gilad Arnoldd0c71752013-12-06 11:48:45 -0800396
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700397 def HandleHostInfoPing(self, ip):
398 """Returns host info dictionary for the given IP in JSON format."""
399 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800400 if ip in self.host_infos.table:
401 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
402
403 def HandleHostLogPing(self, ip):
404 """Returns a complete log of events for host in JSON format."""
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700405 # If all events requested, return a dictionary of logs keyed by IP address.
Gilad Arnold286a0062012-01-12 13:47:02 -0800406 if ip == 'all':
407 return json.dumps(
408 dict([(key, self.host_infos.table[key].log)
409 for key in self.host_infos.table]))
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700410
411 # Otherwise we're looking for a specific IP address, so find its log.
Gilad Arnold286a0062012-01-12 13:47:02 -0800412 if ip in self.host_infos.table:
413 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700414
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700415 # If no events were logged for this IP, return an empty log.
416 return json.dumps([])
417
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700418 def HandleSetUpdatePing(self, ip, label):
419 """Sets forced_update_label for a given host."""
420 assert ip, 'No ip provided.'
421 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800422 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label