blob: c03d770a9de66dc7a142051a443495f2fb93072e [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
rtc@google.com64244662009-11-12 00:52:08 +00005from buildutil import BuildObject
rtc@google.comded22402009-10-26 22:36:21 +00006from xml.dom import minidom
Don Garrettf90edf02010-11-16 17:36:14 -08007
Chris Sosa7c931362010-10-11 19:49:01 -07008import cherrypy
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08009import datetime
Dale Curtisc9aaf3a2011-08-09 15:47:40 -070010import json
rtc@google.comded22402009-10-26 22:36:21 +000011import os
Darin Petkov798fe7d2010-03-22 15:18:13 -070012import shutil
Chris Sosa05491b12010-11-08 17:14:16 -080013import subprocess
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
Chris Sosa05491b12010-11-08 17:14:16 -080017
Chris Sosa7c931362010-10-11 19:49:01 -070018def _LogMessage(message):
19 cherrypy.log(message, 'UPDATE')
rtc@google.comded22402009-10-26 22:36:21 +000020
Chris Sosa417e55d2011-01-25 16:40:48 -080021UPDATE_FILE = 'update.gz'
22STATEFUL_FILE = 'stateful.tgz'
23CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 15:46:22 -070024
Don Garrett0ad09372010-12-06 16:20:30 -080025
26def _ChangeUrlPort(url, new_port):
27 """Return the URL passed in with a different port"""
28 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
29 host_port = netloc.split(':')
30
31 if len(host_port) == 1:
32 host_port.append(new_port)
33 else:
34 host_port[1] = new_port
35
36 print host_port
37 netloc = "%s:%s" % tuple(host_port)
38
39 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
40
41
Gilad Arnold286a0062012-01-12 13:47:02 -080042class HostInfo:
43 """Records information about an individual host.
44
45 Members:
46 attrs: Static attributes (legacy)
47 log: Complete log of recorded client entries
48 """
49
50 def __init__(self):
51 # A dictionary of current attributes pertaining to the host.
52 self.attrs = {}
53
54 # A list of pairs consisting of a timestamp and a dictionary of recorded
55 # attributes.
56 self.log = []
57
58 def __repr__(self):
59 return 'attrs=%s, log=%s' % (self.attrs, self.log)
60
61 def AddLogEntry(self, entry):
62 """Append a new log entry."""
63 # Append a timestamp.
64 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
65 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
66 # Add entry to hosts' message log.
67 self.log.append(entry)
68
69 def SetAttr(self, attr, value):
70 """Set an attribute value."""
71 self.attrs[attr] = value
72
73 def GetAttr(self, attr):
74 """Returns the value of an attribute."""
75 if attr in self.attrs:
76 return self.attrs[attr]
77
78 def PopAttr(self, attr, default):
79 """Returns and deletes a particular attribute."""
80 return self.attrs.pop(attr, default)
81
82
83class HostInfoTable:
84 """Records information about a set of hosts who engage in update activity.
85
86 Members:
87 table: Table of information on hosts.
88 """
89
90 def __init__(self):
91 # A dictionary of host information. Keys are normally IP addresses.
92 self.table = {}
93
94 def __repr__(self):
95 return '%s' % self.table
96
97 def GetInitHostInfo(self, host_id):
98 """Return a host's info object, or create a new one if none exists."""
99 return self.table.setdefault(host_id, HostInfo())
100
101 def GetHostInfo(self, host_id):
102 """Return an info object for given host, if such exists."""
103 if host_id in self.table:
104 return self.table[host_id]
105
106
rtc@google.com64244662009-11-12 00:52:08 +0000107class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700108 """Class that contains functionality that handles Chrome OS update pings.
109
110 Members:
Dale Curtis723ec472010-11-30 14:06:47 -0800111 serve_only: Serve only pre-built updates. static_dir must contain update.gz
112 and stateful.tgz.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700113 factory_config: Path to the factory config file if handling factory
114 requests.
115 use_test_image: Use chromiumos_test_image.bin rather than the standard.
116 static_url_base: base URL, other than devserver, for update images.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700117 forced_image: Path to an image to use for all updates.
Chris Sosa08d55a22011-01-19 16:08:02 -0800118 forced_payload: Path to pre-generated payload to serve.
119 port: port to host devserver
120 proxy_port: port of local proxy to tell client to connect to you through.
121 src_image: If specified, creates a delta payload from this image.
122 vm: Set for VM images (doesn't patch kernel)
123 board: board for the image. Needed for pre-generating of updates.
124 copy_to_static_root: Copies images generated from the cache to
125 ~/static.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700126 """
rtc@google.comded22402009-10-26 22:36:21 +0000127
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700128 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 14:15:23 -0700129 factory_config_path=None,
Don Garrett0c880e22010-11-17 18:13:37 -0800130 forced_image=None, forced_payload=None,
Don Garrett0ad09372010-12-06 16:20:30 -0800131 port=8080, proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800132 copy_to_static_root=True, private_key=None,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800133 critical_update=False,
Chris Sosae67b78f2010-11-04 17:33:16 -0700134 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700135 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700136 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -0700137 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700138 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -0700139 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700140 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700141 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700142 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700143
Chris Sosa0356d3b2010-09-16 15:46:22 -0700144 self.forced_image = forced_image
Don Garrett0c880e22010-11-17 18:13:37 -0800145 self.forced_payload = forced_payload
Chris Sosa62f720b2010-10-26 21:39:48 -0700146 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800147 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-28 23:42:37 -0700148 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -0700149 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800150 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800151 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800152 self.critical_update = critical_update
Don Garrettfff4c322010-11-19 13:37:12 -0800153
Chris Sosa417e55d2011-01-25 16:40:48 -0800154 # Path to pre-generated file.
155 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700156
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700157 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800158 # information about a given host. A host is identified by its IP address.
159 # The info stored for each host includes a complete log of events for this
160 # host, as well as a dictionary of current attributes derived from events.
161 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700162
Chris Sosa0356d3b2010-09-16 15:46:22 -0700163 def _GetSecondsSinceMidnight(self):
164 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700165 now = time.localtime()
166 return now[3] * 3600 + now[4] * 60 + now[5]
167
Chris Sosa0356d3b2010-09-16 15:46:22 -0700168 def _GetDefaultBoardID(self):
169 """Returns the default board id stored in .default_board."""
170 board_file = '%s/.default_board' % (self.scripts_dir)
171 try:
172 return open(board_file).read()
173 except IOError:
174 return 'x86-generic'
175
176 def _GetLatestImageDir(self, board_id):
177 """Returns the latest image dir based on shell script."""
178 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
179 return os.popen(cmd).read().strip()
180
181 def _GetVersionFromDir(self, image_dir):
182 """Returns the version of the image based on the name of the directory."""
183 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700184 parts = latest_version.split('-')
185 if len(parts) == 2:
186 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
187 # TODO(derat): Remove the code for old-style versions after 20120101.
188 return parts[0]
189 else:
190 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
191 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700192
193 def _CanUpdate(self, client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800194 """Returns true if the latest_version is greater than the client_version.
195 """
Chris Sosa7c931362010-10-11 19:49:01 -0700196 _LogMessage('client version %s latest version %s'
Don Garrettf90edf02010-11-16 17:36:14 -0800197 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700198
199 client_tokens = client_version.replace('_', '').split('.')
200 # If the client has an old four-token version like "0.16.892.0", drop the
201 # first two tokens -- we use versions like "892.0.0" now.
202 # TODO(derat): Remove the code for old-style versions after 20120101.
203 if len(client_tokens) == 4:
204 client_tokens = client_tokens[2:]
205
206 latest_tokens = latest_version.replace('_', '').split('.')
207 if len(latest_tokens) == 4:
208 latest_tokens = latest_tokens[2:]
209
210 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700211 if int(latest_tokens[i]) == int(client_tokens[i]):
212 continue
213 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700214
215 # Favor four-token new-style versions on the server over old-style versions
216 # on the client if everything else matches.
217 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700218
Chris Sosa0356d3b2010-09-16 15:46:22 -0700219 def _UnpackZip(self, image_dir):
220 """Unpacks an image.zip into a given directory."""
221 image = os.path.join(image_dir, self._GetImageName())
222 if os.path.exists(image):
223 return True
224 else:
225 # -n, never clobber an existing file, in case we get invoked
226 # simultaneously by multiple request handlers. This means that
227 # we're assuming each image.zip file lives in a versioned
228 # directory (a la Buildbot).
229 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
230
231 def _GetImageName(self):
232 """Returns the name of the image that should be used."""
233 if self.use_test_image:
234 image_name = 'chromiumos_test_image.bin'
235 else:
236 image_name = 'chromiumos_image.bin'
237 return image_name
238
Chris Sosa0356d3b2010-09-16 15:46:22 -0700239 def _GetSize(self, update_path):
240 """Returns the size of the file given."""
241 return os.path.getsize(update_path)
242
243 def _GetHash(self, update_path):
244 """Returns the sha1 of the file given."""
245 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
246 % update_path)
247 return os.popen(cmd).read().rstrip()
248
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700249 def _IsDeltaFormatFile(self, filename):
250 try:
251 file_handle = open(filename, 'r')
252 delta_magic = 'CrAU'
253 magic = file_handle.read(len(delta_magic))
254 return magic == delta_magic
255 except Exception:
256 return False
257
Darin Petkov91436cb2010-09-28 08:52:17 -0700258 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
259 # it takes advantage of reduced I/O and multiple processors. Something like:
260 # % tee < FILE > /dev/null \
261 # >( openssl dgst -sha256 -binary | openssl base64 ) \
262 # >( openssl sha1 -binary | openssl base64 )
263 def _GetSHA256(self, update_path):
264 """Returns the sha256 of the file given."""
265 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
266 update_path)
267 return os.popen(cmd).read().rstrip()
268
Don Garrettf90edf02010-11-16 17:36:14 -0800269 def _GetMd5(self, update_path):
270 """Returns the md5 checksum of the file given."""
271 cmd = ("md5sum %s | awk '{print $1}'" % update_path)
272 return os.popen(cmd).read().rstrip()
273
Don Garrett0c880e22010-11-17 18:13:37 -0800274 def _Copy(self, source, dest):
275 """Copies a file from dest to source (if different)"""
276 _LogMessage('Copy File %s -> %s' % (source, dest))
277 if os.path.lexists(dest):
Don Garrettf90edf02010-11-16 17:36:14 -0800278 os.remove(dest)
Don Garrett0c880e22010-11-17 18:13:37 -0800279 shutil.copy(source, dest)
Don Garrettf90edf02010-11-16 17:36:14 -0800280
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700281 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700282 """Returns a payload to the client corresponding to a new update.
283
284 Args:
285 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700286 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700287 size: size of update blob
288 url: where to find update blob
289 Returns:
290 Xml string to be passed back to client.
291 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700292 delta = 'false'
293 if is_delta_format:
294 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000295 payload = """<?xml version="1.0" encoding="UTF-8"?>
296 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700297 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000298 <app appid="{%s}" status="ok">
299 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700300 <updatecheck
301 codebase="%s"
302 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700303 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700304 needsadmin="false"
305 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700306 IsDelta="%s"
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800307 status="ok"
308 %s/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000309 </app>
310 </gupdate>
311 """
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800312 extra_attributes = []
313 if self.critical_update:
314 # The date string looks like '20111115' (2011-11-15). As of writing,
315 # there's no particular format for the deadline value that the
316 # client expects -- it's just empty vs. non-empty.
317 date_str = datetime.date.today().strftime('%Y%m%d')
318 extra_attributes.append('deadline="%s"' % date_str)
319 xml = payload % (self._GetSecondsSinceMidnight(),
320 self.app_id, url, hash, sha256, size, delta,
321 ' '.join(extra_attributes))
322 _LogMessage('Generated update payload: %s' % xml)
323 return xml
rtc@google.comded22402009-10-26 22:36:21 +0000324
rtc@google.com21a5ca32009-11-04 18:23:23 +0000325 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700326 """Returns a payload to the client corresponding to no update."""
Darin Petkov845f1172011-01-05 14:45:24 -0800327 payload = """<?xml version="1.0" encoding="UTF-8"?>
328 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
329 <daystart elapsed_seconds="%s"/>
330 <app appid="{%s}" status="ok">
331 <ping status="ok"/>
332 <updatecheck status="noupdate"/>
333 </app>
334 </gupdate>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000335 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700336 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000337
Don Garrettf90edf02010-11-16 17:36:14 -0800338 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700339 """Generates an update gz given a full path to an image.
340
341 Args:
342 image_path: Full path to image.
343 Returns:
344 Path to created update_payload or None on error.
345 """
Don Garrettfff4c322010-11-19 13:37:12 -0800346 update_path = os.path.join(output_dir, UPDATE_FILE)
Chris Sosa7c931362010-10-11 19:49:01 -0700347 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700348
Chris Sosa0f1ec842011-02-14 16:33:22 -0800349 update_command = [
Zdenek Behan59d8aa72011-02-24 01:09:02 +0100350 '%s/cros_generate_update_payload' % self.devserver_dir,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800351 '--image="%s"' % image_path,
352 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800353 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700354
Chris Sosa0f1ec842011-02-14 16:33:22 -0800355 if src_image: update_command.append('--src_image="%s"' % src_image)
356 if not self.vm: update_command.append('--patch_kernel')
357 if self.private_key: update_command.append('--private_key="%s"' %
358 self.private_key)
359
360 update_string = ' '.join(update_command)
361 _LogMessage('Running ' + update_string)
362 if os.system(update_string) != 0:
Chris Sosa417e55d2011-01-25 16:40:48 -0800363 _LogMessage('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700364 return None
365
Don Garrettfff4c322010-11-19 13:37:12 -0800366 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700367
Don Garrettf90edf02010-11-16 17:36:14 -0800368 def GenerateStatefulFile(self, image_path, output_dir):
369 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700370
371 Args:
372 image_path: Full path to image.
373 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800374 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800375 Raises:
376 A subprocess exception if the update generator fails to generate a
377 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700378 """
Don Garrettfff4c322010-11-19 13:37:12 -0800379 output_gz = os.path.join(output_dir, STATEFUL_FILE)
Chris Sosa908fd6f2010-11-10 17:31:18 -0800380 subprocess.check_call(
Zdenek Behan59d8aa72011-02-24 01:09:02 +0100381 ['%s/cros_generate_stateful_update_payload' % self.devserver_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800382 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800383 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800384 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800385 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700386
Don Garrettf90edf02010-11-16 17:36:14 -0800387 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
388 """Find directory to store a cached update.
389
Chris Sosa744e1472011-09-07 19:32:50 -0700390 Given one, or two images for an update, this finds which
391 cache directory should hold the update files, even if they don't exist
392 yet. The directory will be inside static_image_dir, and of the form:
Don Garrettf90edf02010-11-16 17:36:14 -0800393
Chris Sosa744e1472011-09-07 19:32:50 -0700394 Non-delta updates:
395 CACHE_DIR/12345678
396 Delta updates:
397 CACHE_DIR/12345678_12345678
Don Garrettf90edf02010-11-16 17:36:14 -0800398
Chris Sosa744e1472011-09-07 19:32:50 -0700399 If self.private_key -- Signed updates:
400 CACHE_DIR/from_above+12345678
401 """
402 sub_dir = self._GetMd5(dest_image)
403 if src_image:
404 sub_dir = '%s_%s' % (self._GetMd5(src_image), sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800405
Chris Sosa744e1472011-09-07 19:32:50 -0700406 if self.private_key:
407 sub_dir = '%s+%s' % (sub_dir, self._GetMd5(self.private_key))
408
Chris Sosa9fba7562012-01-31 10:15:47 -0800409 if not self.vm:
410 sub_dir = '%s+patched_kernel' % sub_dir
411
Chris Sosa744e1472011-09-07 19:32:50 -0700412 return os.path.join(CACHE_DIR, sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800413
Don Garrettfff4c322010-11-19 13:37:12 -0800414 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800415 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700416
Chris Sosade91f672010-11-16 10:05:44 -0800417 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800418 src_image: image we are updating from (Null/empty for non-delta)
419 image_path: full path to the image.
420 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800421 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800422 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800423 """
Don Garrettf90edf02010-11-16 17:36:14 -0800424 update_file = None
425 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700426
Don Garrettf90edf02010-11-16 17:36:14 -0800427 # Actually do the generation
428 _LogMessage('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800429 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800430 image_path,
431 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000432
Don Garrettf90edf02010-11-16 17:36:14 -0800433 if update_file:
434 stateful_update_file = self.GenerateStatefulFile(image_path,
435 output_dir)
436
437 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800438 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800439 else:
440 _LogMessage('Failed to generate update.')
441 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800442
443 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
444 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000445
Chris Sosa0356d3b2010-09-16 15:46:22 -0700446 Args:
447 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700448 static_image_dir: the directory to move images to after generating.
449 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800450 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800451 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700452 """
Don Garrettf90edf02010-11-16 17:36:14 -0800453 _LogMessage('Generating update for src %s image %s' % (self.src_image,
454 image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700455
Chris Sosa417e55d2011-01-25 16:40:48 -0800456 # If it was pregenerated_path, don't regenerate
457 if self.pregenerated_path:
458 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800459
Don Garrettf90edf02010-11-16 17:36:14 -0800460 # Which sub_dir of static_image_dir should hold our cached update image
461 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
462 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir)
463
Chris Sosa417e55d2011-01-25 16:40:48 -0800464 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
465
Don Garrettf90edf02010-11-16 17:36:14 -0800466 # The cached payloads exist in a cache dir
467 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800468 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800469 cache_stateful_payload = os.path.join(static_image_dir,
470 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800471 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800472
Chris Sosa417e55d2011-01-25 16:40:48 -0800473 # Check to see if this cache directory is valid.
474 if not os.path.exists(cache_update_payload) or not os.path.exists(
475 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800476 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800477 # Clean up stale state.
478 os.system('rm -rf "%s"' % full_cache_dir)
479 os.makedirs(full_cache_dir)
480 return_path = self.GenerateUpdateImage(image_path,
481 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800482
Chris Sosa417e55d2011-01-25 16:40:48 -0800483 # Clean up cache dir since it's not valid.
484 if not return_path:
485 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800486 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800487
488 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800489
Chris Sosa08d55a22011-01-19 16:08:02 -0800490 # Generation complete, copy if requested.
491 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800492 # The final results exist directly in static
493 update_payload = os.path.join(static_image_dir,
494 UPDATE_FILE)
495 stateful_payload = os.path.join(static_image_dir,
496 STATEFUL_FILE)
Chris Sosa08d55a22011-01-19 16:08:02 -0800497 self._Copy(cache_update_payload, update_payload)
498 self._Copy(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800499 return UPDATE_FILE
500 else:
501 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700502
503 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800504 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700505 """Generates an update using the latest image that has been built.
506
507 This will only generate an update if the newest update is newer than that
508 on the client or client_version is 'ForcedUpdate'.
509
510 Args:
511 board_id: Name of the board.
512 client_version: Current version of the client or 'ForcedUpdate'
513 static_image_dir: the directory to move images to after generating.
514 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800515 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700516 """
517 latest_image_dir = self._GetLatestImageDir(board_id)
518 latest_version = self._GetVersionFromDir(latest_image_dir)
519 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
520
Chris Sosa7c931362010-10-11 19:49:01 -0700521 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700522 latest_image_path)
523
524 # Check to see whether or not we should update.
525 if client_version != 'ForcedUpdate' and not self._CanUpdate(
526 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700527 _LogMessage('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800528 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700529
Don Garrettf90edf02010-11-16 17:36:14 -0800530 return self.GenerateUpdateImageWithCache(latest_image_path,
531 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700532
Andrew de los Reyes52620802010-04-12 13:40:07 -0700533 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
534 """Imports a factory-floor server configuration file. The file should
535 be in this format:
536 config = [
537 {
538 'qual_ids': set([1, 2, 3, "x86-generic"]),
539 'factory_image': 'generic-factory.gz',
540 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
541 'release_image': 'generic-release.gz',
542 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
543 'oempartitionimg_image': 'generic-oem.gz',
544 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700545 'efipartitionimg_image': 'generic-efi.gz',
546 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700547 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800548 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800549 'firmware_image': 'generic-firmware.gz',
550 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700551 },
552 {
553 'qual_ids': set([6]),
554 'factory_image': '6-factory.gz',
555 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
556 'release_image': '6-release.gz',
557 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
558 'oempartitionimg_image': '6-oem.gz',
559 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700560 'efipartitionimg_image': '6-efi.gz',
561 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700562 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800563 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800564 'firmware_image': '6-firmware.gz',
565 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700566 },
567 ]
568 The server will look for the files by name in the static files
569 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700570
Andrew de los Reyes52620802010-04-12 13:40:07 -0700571 If validate_checksums is True, validates checksums and exits. If
572 a checksum mismatch is found, it's printed to the screen.
573 """
574 f = open(filename, 'r')
575 output = {}
576 exec(f.read(), output)
577 self.factory_config = output['config']
578 success = True
579 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800580 for key in stanza.copy().iterkeys():
581 suffix = '_image'
582 if key.endswith(suffix):
583 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700584 stanza[kind + '_size'] = self._GetSize(os.path.join(
585 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800586 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700587 factory_checksum = self._GetHash(os.path.join(self.static_dir,
588 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800589 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700590 print ('Error: checksum mismatch for %s. Expected "%s" but file '
591 'has checksum "%s".' % (stanza[kind + '_image'],
592 stanza[kind + '_checksum'],
593 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800594 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700595
Andrew de los Reyes52620802010-04-12 13:40:07 -0700596 if validate_checksums:
597 if success is False:
598 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700599
Andrew de los Reyes52620802010-04-12 13:40:07 -0700600 print 'Config file looks good.'
601
602 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700603 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700604 for stanza in self.factory_config:
605 if board_id not in stanza['qual_ids']:
606 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700607 if kind + '_image' not in stanza:
608 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700609 return (stanza[kind + '_image'],
610 stanza[kind + '_checksum'],
611 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700612 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000613
Chris Sosa7c931362010-10-11 19:49:01 -0700614 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700615 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
616 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700617 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700618 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700619 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700620 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700621 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700622 # Factory install is using memento updater which is using the sha-1 hash so
623 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700624 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700625
Chris Sosa151643e2010-10-28 14:40:57 -0700626 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
627 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800628 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800629
Don Garrettf90edf02010-11-16 17:36:14 -0800630 Returns:
631 file name relative to static_image_dir on success.
632 """
Dale Curtis723ec472010-11-30 14:06:47 -0800633 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
634 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
635
Don Garrett0c880e22010-11-17 18:13:37 -0800636 if self.forced_payload:
637 # If the forced payload is not already in our static_image_dir,
638 # copy it there.
Don Garrettee25e552010-11-23 12:09:35 -0800639 src_path = os.path.abspath(self.forced_payload)
Don Garrettee25e552010-11-23 12:09:35 -0800640 src_stateful = os.path.join(os.path.dirname(src_path),
641 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800642
643 # Only copy the files if the source directory is different from dest.
644 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
645 self._Copy(src_path, dest_path)
646
647 # The stateful payload is optional.
648 if os.path.exists(src_stateful):
649 self._Copy(src_stateful, dest_stateful)
650 else:
651 _LogMessage('WARN: %s not found. Expected for dev and test builds.' %
652 STATEFUL_FILE)
653 if os.path.exists(dest_stateful):
654 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800655
Don Garrettfff4c322010-11-19 13:37:12 -0800656 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800657 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800658 return self.GenerateUpdateImageWithCache(
659 self.forced_image,
660 static_image_dir=static_image_dir)
661 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800662 # Warn if update or stateful files can't be found.
663 if not os.path.exists(dest_path):
664 _LogMessage('WARN: %s not found. Expected for dev and test builds.' %
665 UPDATE_FILE)
666
667 if not os.path.exists(dest_stateful):
668 _LogMessage('WARN: %s not found. Expected for dev and test builds.' %
669 STATEFUL_FILE)
670
671 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800672 else:
673 if board_id:
674 return self.GenerateLatestUpdateImage(board_id,
675 client_version,
676 static_image_dir)
677
Chris Sosa417e55d2011-01-25 16:40:48 -0800678 _LogMessage('Failed to genereate update. '
679 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800680 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700681
682 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800683 """Pre-generates an update and prints out the relative path it.
684
685 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800686 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700687 # Does not work with factory config.
688 assert(not self.factory_config)
689 _LogMessage('Pre-generating the update payload.')
690 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800691 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
692 self.board, '0.0.0.0', self.static_dir)
693 if pregenerated_update:
694 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
695
696 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700697
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700698 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700699 """Handles an update ping from an update client.
700
701 Args:
702 data: xml blob from client.
703 label: optional label for the update.
704 Returns:
705 Update payload message for client.
706 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700707 # Set hostname as the hostname that the client is calling to and set up
708 # the url base.
709 self.hostname = cherrypy.request.base
710 if self.urlbase:
711 static_urlbase = self.urlbase
712 elif self.serve_only:
713 static_urlbase = '%s/static/archive' % self.hostname
714 else:
715 static_urlbase = '%s/static' % self.hostname
716
Don Garrett0ad09372010-12-06 16:20:30 -0800717 # If we have a proxy port, adjust the URL we instruct the client to
718 # use to go through the proxy.
719 if self.proxy_port:
720 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
721
Chris Sosa9841e1c2010-10-14 10:51:45 -0700722 _LogMessage('Using static url base %s' % static_urlbase)
723 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700724
Chris Sosa9841e1c2010-10-14 10:51:45 -0700725 update_dom = minidom.parseString(data)
726 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700727
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700728 # Determine request IP, strip any IPv6 data for simplicity.
729 client_ip = cherrypy.request.remote.ip.split(':')[-1]
730
Gilad Arnold286a0062012-01-12 13:47:02 -0800731 # Obtain (or init) info object for this client.
732 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
733
734 # Initialize an empty dictionary for event attributes.
735 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700736
737 # Store event details in the host info dictionary for API usage.
738 event = root.getElementsByTagName('o:event')
739 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800740 event_result = int(event[0].getAttribute('eventresult'))
741 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700742 client_previous_version = (event[0].getAttribute('previousversion')
743 if event[0].hasAttribute('previousversion')
744 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800745 # Store attributes to legacy host info structure
746 curr_host_info.attrs['last_event_status'] = event_result
747 curr_host_info.attrs['last_event_type'] = event_type
748 # Add attributes to log message
749 log_message['event_result'] = event_result
750 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700751 if client_previous_version is not None:
752 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800753
754 # Get information about the requester.
755 query = root.getElementsByTagName('o:app')[0]
756 if query:
757 client_version = query.getAttribute('version')
758 channel = query.getAttribute('track')
759 board_id = (query.hasAttribute('board') and query.getAttribute('board')
760 or self._GetDefaultBoardID())
761 # Add attributes to log message
762 log_message['version'] = client_version
763 log_message['track'] = channel
764 log_message['board'] = board_id
765
766 # Log client's message
767 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700768
Chris Sosa0356d3b2010-09-16 15:46:22 -0700769 # We only generate update payloads for updatecheck requests.
770 update_check = root.getElementsByTagName('o:updatecheck')
771 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700772 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700773 # TODO(sosa): Generate correct non-updatecheck payload to better test
774 # update clients.
775 return self.GetNoUpdatePayload()
776
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700777 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800778 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700779
780 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 13:47:02 -0800781 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700782 if forced_update:
783 label = forced_update
784
Chris Sosa0356d3b2010-09-16 15:46:22 -0700785 # Separate logic as Factory requests have static url's that override
786 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700787 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700788 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700789 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700790 static_image_dir = self.static_dir
791 if label:
792 static_image_dir = os.path.join(static_image_dir, label)
793
Don Garrettf90edf02010-11-16 17:36:14 -0800794 payload_path = self.GenerateUpdatePayloadForNonFactory(board_id,
795 client_version,
796 static_image_dir)
797 if payload_path:
798 filename = os.path.join(static_image_dir, payload_path)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700799 hash = self._GetHash(filename)
800 sha256 = self._GetSHA256(filename)
801 size = self._GetSize(filename)
802 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700803 if label:
Don Garrettf90edf02010-11-16 17:36:14 -0800804 url = '%s/%s/%s' % (static_urlbase, label, payload_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700805 else:
Don Garrettf90edf02010-11-16 17:36:14 -0800806 url = '%s/%s' % (static_urlbase, payload_path)
Chris Sosa5d342a22010-09-28 16:54:41 -0700807
Chris Sosa7c931362010-10-11 19:49:01 -0700808 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700809 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700810 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700811 return self.GetNoUpdatePayload()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700812
813 def HandleHostInfoPing(self, ip):
814 """Returns host info dictionary for the given IP in JSON format."""
815 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800816 if ip in self.host_infos.table:
817 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
818
819 def HandleHostLogPing(self, ip):
820 """Returns a complete log of events for host in JSON format."""
821 if ip == 'all':
822 return json.dumps(
823 dict([(key, self.host_infos.table[key].log)
824 for key in self.host_infos.table]))
825 if ip in self.host_infos.table:
826 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700827
828 def HandleSetUpdatePing(self, ip, label):
829 """Sets forced_update_label for a given host."""
830 assert ip, 'No ip provided.'
831 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800832 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label