blob: 548f7944f0c90d08625ae8eeea3aa74d72d63617 [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
5from xml.dom import minidom
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08006import datetime
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07007import json
rtc@google.comded22402009-10-26 22:36:21 +00008import os
Darin Petkov798fe7d2010-03-22 15:18:13 -07009import shutil
Chris Sosa05491b12010-11-08 17:14:16 -080010import subprocess
Darin Petkov2b2ff4b2010-07-27 15:02:09 -070011import time
Don Garrett0ad09372010-12-06 16:20:30 -080012import urlparse
Chris Sosa7c931362010-10-11 19:49:01 -070013
Gilad Arnoldabb352e2012-09-23 01:24:27 -070014import cherrypy
15
16from build_util import BuildObject
Gilad Arnold55a2a372012-10-02 09:46:32 -070017import common_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070018import log_util
Chris Sosa05491b12010-11-08 17:14:16 -080019
Gilad Arnoldc65330c2012-09-20 15:17:48 -070020
21# Module-local log function.
22def _Log(message, *args, **kwargs):
23 return log_util.LogWithTag('UPDATE', message, *args, **kwargs)
24
rtc@google.comded22402009-10-26 22:36:21 +000025
Chris Sosa417e55d2011-01-25 16:40:48 -080026UPDATE_FILE = 'update.gz'
27STATEFUL_FILE = 'stateful.tgz'
28CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 15:46:22 -070029
Don Garrett0ad09372010-12-06 16:20:30 -080030
31def _ChangeUrlPort(url, new_port):
32 """Return the URL passed in with a different port"""
33 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
34 host_port = netloc.split(':')
35
36 if len(host_port) == 1:
37 host_port.append(new_port)
38 else:
39 host_port[1] = new_port
40
41 print host_port
42 netloc = "%s:%s" % tuple(host_port)
43
44 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
45
46
Gilad Arnold286a0062012-01-12 13:47:02 -080047class HostInfo:
48 """Records information about an individual host.
49
50 Members:
51 attrs: Static attributes (legacy)
52 log: Complete log of recorded client entries
53 """
54
55 def __init__(self):
56 # A dictionary of current attributes pertaining to the host.
57 self.attrs = {}
58
59 # A list of pairs consisting of a timestamp and a dictionary of recorded
60 # attributes.
61 self.log = []
62
63 def __repr__(self):
64 return 'attrs=%s, log=%s' % (self.attrs, self.log)
65
66 def AddLogEntry(self, entry):
67 """Append a new log entry."""
68 # Append a timestamp.
69 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
70 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
71 # Add entry to hosts' message log.
72 self.log.append(entry)
73
74 def SetAttr(self, attr, value):
75 """Set an attribute value."""
76 self.attrs[attr] = value
77
78 def GetAttr(self, attr):
79 """Returns the value of an attribute."""
80 if attr in self.attrs:
81 return self.attrs[attr]
82
83 def PopAttr(self, attr, default):
84 """Returns and deletes a particular attribute."""
85 return self.attrs.pop(attr, default)
86
87
88class HostInfoTable:
89 """Records information about a set of hosts who engage in update activity.
90
91 Members:
92 table: Table of information on hosts.
93 """
94
95 def __init__(self):
96 # A dictionary of host information. Keys are normally IP addresses.
97 self.table = {}
98
99 def __repr__(self):
100 return '%s' % self.table
101
102 def GetInitHostInfo(self, host_id):
103 """Return a host's info object, or create a new one if none exists."""
104 return self.table.setdefault(host_id, HostInfo())
105
106 def GetHostInfo(self, host_id):
107 """Return an info object for given host, if such exists."""
108 if host_id in self.table:
109 return self.table[host_id]
110
111
rtc@google.com64244662009-11-12 00:52:08 +0000112class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700113 """Class that contains functionality that handles Chrome OS update pings.
114
115 Members:
Dale Curtis723ec472010-11-30 14:06:47 -0800116 serve_only: Serve only pre-built updates. static_dir must contain update.gz
117 and stateful.tgz.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700118 factory_config: Path to the factory config file if handling factory
119 requests.
120 use_test_image: Use chromiumos_test_image.bin rather than the standard.
121 static_url_base: base URL, other than devserver, for update images.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700122 forced_image: Path to an image to use for all updates.
Chris Sosa08d55a22011-01-19 16:08:02 -0800123 forced_payload: Path to pre-generated payload to serve.
124 port: port to host devserver
125 proxy_port: port of local proxy to tell client to connect to you through.
126 src_image: If specified, creates a delta payload from this image.
127 vm: Set for VM images (doesn't patch kernel)
128 board: board for the image. Needed for pre-generating of updates.
129 copy_to_static_root: Copies images generated from the cache to
130 ~/static.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700131 """
rtc@google.comded22402009-10-26 22:36:21 +0000132
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700133 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 14:15:23 -0700134 factory_config_path=None,
Don Garrett0c880e22010-11-17 18:13:37 -0800135 forced_image=None, forced_payload=None,
Don Garrett0ad09372010-12-06 16:20:30 -0800136 port=8080, proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800137 copy_to_static_root=True, private_key=None,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800138 critical_update=False,
Chris Sosae67b78f2010-11-04 17:33:16 -0700139 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700140 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700141 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -0700142 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700143 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -0700144 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700145 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700146 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700147 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700148
Chris Sosa0356d3b2010-09-16 15:46:22 -0700149 self.forced_image = forced_image
Don Garrett0c880e22010-11-17 18:13:37 -0800150 self.forced_payload = forced_payload
Chris Sosa62f720b2010-10-26 21:39:48 -0700151 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800152 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-28 23:42:37 -0700153 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -0700154 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800155 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800156 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800157 self.critical_update = critical_update
Don Garrettfff4c322010-11-19 13:37:12 -0800158
Chris Sosa417e55d2011-01-25 16:40:48 -0800159 # Path to pre-generated file.
160 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700161
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700162 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800163 # information about a given host. A host is identified by its IP address.
164 # The info stored for each host includes a complete log of events for this
165 # host, as well as a dictionary of current attributes derived from events.
166 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700167
Chris Sosa0356d3b2010-09-16 15:46:22 -0700168 def _GetSecondsSinceMidnight(self):
169 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700170 now = time.localtime()
171 return now[3] * 3600 + now[4] * 60 + now[5]
172
Chris Sosa0356d3b2010-09-16 15:46:22 -0700173 def _GetDefaultBoardID(self):
174 """Returns the default board id stored in .default_board."""
175 board_file = '%s/.default_board' % (self.scripts_dir)
176 try:
177 return open(board_file).read()
178 except IOError:
179 return 'x86-generic'
180
181 def _GetLatestImageDir(self, board_id):
182 """Returns the latest image dir based on shell script."""
183 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
184 return os.popen(cmd).read().strip()
185
186 def _GetVersionFromDir(self, image_dir):
187 """Returns the version of the image based on the name of the directory."""
188 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700189 parts = latest_version.split('-')
190 if len(parts) == 2:
191 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
192 # TODO(derat): Remove the code for old-style versions after 20120101.
193 return parts[0]
194 else:
195 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
196 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700197
198 def _CanUpdate(self, client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800199 """Returns true if the latest_version is greater than the client_version.
200 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700201 _Log('client version %s latest version %s'
202 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700203
204 client_tokens = client_version.replace('_', '').split('.')
205 # If the client has an old four-token version like "0.16.892.0", drop the
206 # first two tokens -- we use versions like "892.0.0" now.
207 # TODO(derat): Remove the code for old-style versions after 20120101.
208 if len(client_tokens) == 4:
209 client_tokens = client_tokens[2:]
210
211 latest_tokens = latest_version.replace('_', '').split('.')
212 if len(latest_tokens) == 4:
213 latest_tokens = latest_tokens[2:]
214
215 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700216 if int(latest_tokens[i]) == int(client_tokens[i]):
217 continue
218 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700219
220 # Favor four-token new-style versions on the server over old-style versions
221 # on the client if everything else matches.
222 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700223
Chris Sosa0356d3b2010-09-16 15:46:22 -0700224 def _UnpackZip(self, image_dir):
225 """Unpacks an image.zip into a given directory."""
226 image = os.path.join(image_dir, self._GetImageName())
227 if os.path.exists(image):
228 return True
229 else:
230 # -n, never clobber an existing file, in case we get invoked
231 # simultaneously by multiple request handlers. This means that
232 # we're assuming each image.zip file lives in a versioned
233 # directory (a la Buildbot).
234 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
235
236 def _GetImageName(self):
237 """Returns the name of the image that should be used."""
238 if self.use_test_image:
239 image_name = 'chromiumos_test_image.bin'
240 else:
241 image_name = 'chromiumos_image.bin'
242 return image_name
243
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700244 def _IsDeltaFormatFile(self, filename):
245 try:
246 file_handle = open(filename, 'r')
247 delta_magic = 'CrAU'
248 magic = file_handle.read(len(delta_magic))
249 return magic == delta_magic
250 except Exception:
251 return False
252
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700253 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700254 """Returns a payload to the client corresponding to a new update.
255
256 Args:
257 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700258 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700259 size: size of update blob
260 url: where to find update blob
261 Returns:
262 Xml string to be passed back to client.
263 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700264 delta = 'false'
265 if is_delta_format:
266 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000267 payload = """<?xml version="1.0" encoding="UTF-8"?>
268 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700269 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000270 <app appid="{%s}" status="ok">
271 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700272 <updatecheck
Jay Srinivasan9a1c4572012-03-16 19:16:58 -0700273 ChromeOSVersion="9999.0.0"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700274 codebase="%s"
275 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700276 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700277 needsadmin="false"
278 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700279 IsDelta="%s"
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800280 status="ok"
281 %s/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000282 </app>
283 </gupdate>
284 """
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800285 extra_attributes = []
286 if self.critical_update:
287 # The date string looks like '20111115' (2011-11-15). As of writing,
288 # there's no particular format for the deadline value that the
289 # client expects -- it's just empty vs. non-empty.
290 date_str = datetime.date.today().strftime('%Y%m%d')
291 extra_attributes.append('deadline="%s"' % date_str)
292 xml = payload % (self._GetSecondsSinceMidnight(),
293 self.app_id, url, hash, sha256, size, delta,
294 ' '.join(extra_attributes))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700295 _Log('Generated update payload: %s' % xml)
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800296 return xml
rtc@google.comded22402009-10-26 22:36:21 +0000297
rtc@google.com21a5ca32009-11-04 18:23:23 +0000298 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700299 """Returns a payload to the client corresponding to no update."""
Darin Petkov845f1172011-01-05 14:45:24 -0800300 payload = """<?xml version="1.0" encoding="UTF-8"?>
301 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
302 <daystart elapsed_seconds="%s"/>
303 <app appid="{%s}" status="ok">
304 <ping status="ok"/>
305 <updatecheck status="noupdate"/>
306 </app>
307 </gupdate>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000308 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700309 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000310
Don Garrettf90edf02010-11-16 17:36:14 -0800311 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700312 """Generates an update gz given a full path to an image.
313
314 Args:
315 image_path: Full path to image.
316 Returns:
317 Path to created update_payload or None on error.
318 """
Don Garrettfff4c322010-11-19 13:37:12 -0800319 update_path = os.path.join(output_dir, UPDATE_FILE)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700320 _Log('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700321
Chris Sosa0f1ec842011-02-14 16:33:22 -0800322 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700323 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-14 16:33:22 -0800324 '--image="%s"' % image_path,
325 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800326 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700327
Chris Sosa0f1ec842011-02-14 16:33:22 -0800328 if src_image: update_command.append('--src_image="%s"' % src_image)
329 if not self.vm: update_command.append('--patch_kernel')
330 if self.private_key: update_command.append('--private_key="%s"' %
331 self.private_key)
332
333 update_string = ' '.join(update_command)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700334 _Log('Running ' + update_string)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800335 if os.system(update_string) != 0:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700336 _Log('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700337 return None
338
Don Garrettfff4c322010-11-19 13:37:12 -0800339 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700340
Don Garrettf90edf02010-11-16 17:36:14 -0800341 def GenerateStatefulFile(self, image_path, output_dir):
342 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700343
344 Args:
345 image_path: Full path to image.
346 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800347 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800348 Raises:
349 A subprocess exception if the update generator fails to generate a
350 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700351 """
Don Garrettfff4c322010-11-19 13:37:12 -0800352 output_gz = os.path.join(output_dir, STATEFUL_FILE)
Chris Sosa908fd6f2010-11-10 17:31:18 -0800353 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700354 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-10 17:31:18 -0800355 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800356 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800357 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800358 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700359
Don Garrettf90edf02010-11-16 17:36:14 -0800360 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
361 """Find directory to store a cached update.
362
Gilad Arnold55a2a372012-10-02 09:46:32 -0700363 Given one, or two images for an update, this finds which cache directory
364 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-16 17:36:14 -0800365
Gilad Arnold55a2a372012-10-02 09:46:32 -0700366 Returns:
367 A directory path for storing a cached update, of the following form:
368 Non-delta updates:
369 CACHE_DIR/<dest_hash>
370 Delta updates:
371 CACHE_DIR/<src_hash>_<dest_hash>
372 Signed updates (self.private_key):
373 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-07 19:32:50 -0700374 """
Gilad Arnold55a2a372012-10-02 09:46:32 -0700375 update_dir = ''
Chris Sosa744e1472011-09-07 19:32:50 -0700376 if src_image:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700377 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-16 17:36:14 -0800378
Gilad Arnold55a2a372012-10-02 09:46:32 -0700379 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-07 19:32:50 -0700380 if self.private_key:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700381 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-07 19:32:50 -0700382
Chris Sosa9fba7562012-01-31 10:15:47 -0800383 if not self.vm:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700384 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 10:15:47 -0800385
Gilad Arnold55a2a372012-10-02 09:46:32 -0700386 return os.path.join(CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800387
Don Garrettfff4c322010-11-19 13:37:12 -0800388 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800389 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700390
Chris Sosade91f672010-11-16 10:05:44 -0800391 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800392 src_image: image we are updating from (Null/empty for non-delta)
393 image_path: full path to the image.
394 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800395 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800396 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800397 """
Don Garrettf90edf02010-11-16 17:36:14 -0800398 update_file = None
399 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700400
Don Garrettf90edf02010-11-16 17:36:14 -0800401 # Actually do the generation
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700402 _Log('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800403 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800404 image_path,
405 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000406
Don Garrettf90edf02010-11-16 17:36:14 -0800407 if update_file:
408 stateful_update_file = self.GenerateStatefulFile(image_path,
409 output_dir)
410
411 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800412 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800413 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700414 _Log('Failed to generate update.')
Chris Sosa417e55d2011-01-25 16:40:48 -0800415 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800416
417 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
418 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000419
Chris Sosa0356d3b2010-09-16 15:46:22 -0700420 Args:
421 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700422 static_image_dir: the directory to move images to after generating.
423 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800424 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800425 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700426 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700427 _Log('Generating update for src %s image %s' % (self.src_image, image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700428
Chris Sosa417e55d2011-01-25 16:40:48 -0800429 # If it was pregenerated_path, don't regenerate
430 if self.pregenerated_path:
431 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800432
Don Garrettf90edf02010-11-16 17:36:14 -0800433 # Which sub_dir of static_image_dir should hold our cached update image
434 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700435 _Log('Caching in sub_dir "%s"' % cache_sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800436
Chris Sosa417e55d2011-01-25 16:40:48 -0800437 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
438
Don Garrettf90edf02010-11-16 17:36:14 -0800439 # The cached payloads exist in a cache dir
440 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800441 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800442 cache_stateful_payload = os.path.join(static_image_dir,
443 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800444 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800445
Chris Sosa417e55d2011-01-25 16:40:48 -0800446 # Check to see if this cache directory is valid.
447 if not os.path.exists(cache_update_payload) or not os.path.exists(
448 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800449 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800450 # Clean up stale state.
451 os.system('rm -rf "%s"' % full_cache_dir)
452 os.makedirs(full_cache_dir)
453 return_path = self.GenerateUpdateImage(image_path,
454 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800455
Chris Sosa417e55d2011-01-25 16:40:48 -0800456 # Clean up cache dir since it's not valid.
457 if not return_path:
458 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800459 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800460
461 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800462
Chris Sosa08d55a22011-01-19 16:08:02 -0800463 # Generation complete, copy if requested.
464 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800465 # The final results exist directly in static
466 update_payload = os.path.join(static_image_dir,
467 UPDATE_FILE)
468 stateful_payload = os.path.join(static_image_dir,
469 STATEFUL_FILE)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700470 common_util.CopyFile(cache_update_payload, update_payload)
471 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800472 return UPDATE_FILE
473 else:
474 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700475
476 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800477 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700478 """Generates an update using the latest image that has been built.
479
480 This will only generate an update if the newest update is newer than that
481 on the client or client_version is 'ForcedUpdate'.
482
483 Args:
484 board_id: Name of the board.
485 client_version: Current version of the client or 'ForcedUpdate'
486 static_image_dir: the directory to move images to after generating.
487 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800488 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700489 """
490 latest_image_dir = self._GetLatestImageDir(board_id)
491 latest_version = self._GetVersionFromDir(latest_image_dir)
492 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
493
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700494 _Log('Preparing to generate update from latest built image %s.' %
495 latest_image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700496
497 # Check to see whether or not we should update.
498 if client_version != 'ForcedUpdate' and not self._CanUpdate(
499 client_version, latest_version):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700500 _Log('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800501 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700502
Don Garrettf90edf02010-11-16 17:36:14 -0800503 return self.GenerateUpdateImageWithCache(latest_image_path,
504 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700505
Andrew de los Reyes52620802010-04-12 13:40:07 -0700506 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
507 """Imports a factory-floor server configuration file. The file should
508 be in this format:
509 config = [
510 {
511 'qual_ids': set([1, 2, 3, "x86-generic"]),
512 'factory_image': 'generic-factory.gz',
513 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
514 'release_image': 'generic-release.gz',
515 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
516 'oempartitionimg_image': 'generic-oem.gz',
517 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700518 'efipartitionimg_image': 'generic-efi.gz',
519 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700520 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800521 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800522 'firmware_image': 'generic-firmware.gz',
523 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700524 },
525 {
526 'qual_ids': set([6]),
527 'factory_image': '6-factory.gz',
528 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
529 'release_image': '6-release.gz',
530 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
531 'oempartitionimg_image': '6-oem.gz',
532 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700533 'efipartitionimg_image': '6-efi.gz',
534 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700535 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800536 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800537 'firmware_image': '6-firmware.gz',
538 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700539 },
540 ]
541 The server will look for the files by name in the static files
542 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700543
Andrew de los Reyes52620802010-04-12 13:40:07 -0700544 If validate_checksums is True, validates checksums and exits. If
545 a checksum mismatch is found, it's printed to the screen.
546 """
547 f = open(filename, 'r')
548 output = {}
549 exec(f.read(), output)
550 self.factory_config = output['config']
551 success = True
552 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800553 for key in stanza.copy().iterkeys():
554 suffix = '_image'
555 if key.endswith(suffix):
556 kind = key[:-len(suffix)]
Gilad Arnold55a2a372012-10-02 09:46:32 -0700557 stanza[kind + '_size'] = common_util.GetFileSize(os.path.join(
Chris Sosa0356d3b2010-09-16 15:46:22 -0700558 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800559 if validate_checksums:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700560 factory_checksum = common_util.GetFileSha1(
561 os.path.join(self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800562 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700563 print ('Error: checksum mismatch for %s. Expected "%s" but file '
564 'has checksum "%s".' % (stanza[kind + '_image'],
565 stanza[kind + '_checksum'],
566 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800567 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700568
Andrew de los Reyes52620802010-04-12 13:40:07 -0700569 if validate_checksums:
570 if success is False:
571 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700572
Andrew de los Reyes52620802010-04-12 13:40:07 -0700573 print 'Config file looks good.'
574
575 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700576 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700577 for stanza in self.factory_config:
578 if board_id not in stanza['qual_ids']:
579 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700580 if kind + '_image' not in stanza:
581 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700582 return (stanza[kind + '_image'],
583 stanza[kind + '_checksum'],
584 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700585 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000586
Chris Sosa7c931362010-10-11 19:49:01 -0700587 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700588 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
589 if filename is None:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700590 _Log('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700591 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700592 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700593 is_delta_format = self._IsDeltaFormatFile(filename)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700594 _Log('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700595 # Factory install is using memento updater which is using the sha-1 hash so
596 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700597 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700598
Chris Sosa151643e2010-10-28 14:40:57 -0700599 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
600 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800601 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800602
Don Garrettf90edf02010-11-16 17:36:14 -0800603 Returns:
604 file name relative to static_image_dir on success.
605 """
Dale Curtis723ec472010-11-30 14:06:47 -0800606 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
607 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
608
Don Garrett0c880e22010-11-17 18:13:37 -0800609 if self.forced_payload:
610 # If the forced payload is not already in our static_image_dir,
611 # copy it there.
Don Garrettee25e552010-11-23 12:09:35 -0800612 src_path = os.path.abspath(self.forced_payload)
Don Garrettee25e552010-11-23 12:09:35 -0800613 src_stateful = os.path.join(os.path.dirname(src_path),
614 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800615
616 # Only copy the files if the source directory is different from dest.
617 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700618 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 12:09:35 -0800619
620 # The stateful payload is optional.
621 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700622 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 12:09:35 -0800623 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700624 _Log('WARN: %s not found. Expected for dev and test builds.' %
625 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800626 if os.path.exists(dest_stateful):
627 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800628
Don Garrettfff4c322010-11-19 13:37:12 -0800629 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800630 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800631 return self.GenerateUpdateImageWithCache(
632 self.forced_image,
633 static_image_dir=static_image_dir)
634 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800635 # Warn if update or stateful files can't be found.
636 if not os.path.exists(dest_path):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700637 _Log('WARN: %s not found. Expected for dev and test builds.' %
638 UPDATE_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800639
640 if not os.path.exists(dest_stateful):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700641 _Log('WARN: %s not found. Expected for dev and test builds.' %
642 STATEFUL_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800643
644 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800645 else:
646 if board_id:
647 return self.GenerateLatestUpdateImage(board_id,
648 client_version,
649 static_image_dir)
650
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700651 _Log('Failed to genereate update. '
652 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800653 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700654
655 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800656 """Pre-generates an update and prints out the relative path it.
657
658 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800659 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700660 # Does not work with factory config.
661 assert(not self.factory_config)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700662 _Log('Pre-generating the update payload.')
Chris Sosa2c048f12010-10-27 16:05:27 -0700663 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800664 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
665 self.board, '0.0.0.0', self.static_dir)
666 if pregenerated_update:
667 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
668
669 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700670
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700671 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700672 """Handles an update ping from an update client.
673
674 Args:
675 data: xml blob from client.
676 label: optional label for the update.
677 Returns:
678 Update payload message for client.
679 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700680 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 16:26:10 -0700681 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
682 # be in X-Forwarded-Host.
683 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
684 if x_forwarded_host:
685 self.hostname = 'http://' + x_forwarded_host
686 else:
687 self.hostname = cherrypy.request.base
688
Chris Sosa9841e1c2010-10-14 10:51:45 -0700689 if self.urlbase:
690 static_urlbase = self.urlbase
691 elif self.serve_only:
692 static_urlbase = '%s/static/archive' % self.hostname
693 else:
694 static_urlbase = '%s/static' % self.hostname
695
Don Garrett0ad09372010-12-06 16:20:30 -0800696 # If we have a proxy port, adjust the URL we instruct the client to
697 # use to go through the proxy.
698 if self.proxy_port:
699 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
700
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700701 _Log('Using static url base %s' % static_urlbase)
702 _Log('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700703
Chris Sosa9841e1c2010-10-14 10:51:45 -0700704 update_dom = minidom.parseString(data)
705 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700706
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700707 # Determine request IP, strip any IPv6 data for simplicity.
708 client_ip = cherrypy.request.remote.ip.split(':')[-1]
709
Gilad Arnold286a0062012-01-12 13:47:02 -0800710 # Obtain (or init) info object for this client.
711 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
712
713 # Initialize an empty dictionary for event attributes.
714 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700715
716 # Store event details in the host info dictionary for API usage.
717 event = root.getElementsByTagName('o:event')
718 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800719 event_result = int(event[0].getAttribute('eventresult'))
720 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700721 client_previous_version = (event[0].getAttribute('previousversion')
722 if event[0].hasAttribute('previousversion')
723 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800724 # Store attributes to legacy host info structure
725 curr_host_info.attrs['last_event_status'] = event_result
726 curr_host_info.attrs['last_event_type'] = event_type
727 # Add attributes to log message
728 log_message['event_result'] = event_result
729 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700730 if client_previous_version is not None:
731 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800732
733 # Get information about the requester.
734 query = root.getElementsByTagName('o:app')[0]
735 if query:
736 client_version = query.getAttribute('version')
737 channel = query.getAttribute('track')
738 board_id = (query.hasAttribute('board') and query.getAttribute('board')
739 or self._GetDefaultBoardID())
740 # Add attributes to log message
741 log_message['version'] = client_version
742 log_message['track'] = channel
743 log_message['board'] = board_id
744
745 # Log client's message
746 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700747
Chris Sosa0356d3b2010-09-16 15:46:22 -0700748 # We only generate update payloads for updatecheck requests.
749 update_check = root.getElementsByTagName('o:updatecheck')
750 if not update_check:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700751 _Log('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700752 # TODO(sosa): Generate correct non-updatecheck payload to better test
753 # update clients.
754 return self.GetNoUpdatePayload()
755
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700756 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800757 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700758
759 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 13:47:02 -0800760 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700761 if forced_update:
762 label = forced_update
763
Chris Sosa0356d3b2010-09-16 15:46:22 -0700764 # Separate logic as Factory requests have static url's that override
765 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700766 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700767 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700768 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700769 static_image_dir = self.static_dir
770 if label:
771 static_image_dir = os.path.join(static_image_dir, label)
772
Don Garrettf90edf02010-11-16 17:36:14 -0800773 payload_path = self.GenerateUpdatePayloadForNonFactory(board_id,
774 client_version,
775 static_image_dir)
776 if payload_path:
777 filename = os.path.join(static_image_dir, payload_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700778 hash = common_util.GetFileSha1(filename)
779 sha256 = common_util.GetFileSha256(filename)
780 size = common_util.GetFileSize(filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700781 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700782 if label:
Don Garrettf90edf02010-11-16 17:36:14 -0800783 url = '%s/%s/%s' % (static_urlbase, label, payload_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700784 else:
Don Garrettf90edf02010-11-16 17:36:14 -0800785 url = '%s/%s' % (static_urlbase, payload_path)
Chris Sosa5d342a22010-09-28 16:54:41 -0700786
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700787 _Log('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700788 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700789 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700790 return self.GetNoUpdatePayload()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700791
792 def HandleHostInfoPing(self, ip):
793 """Returns host info dictionary for the given IP in JSON format."""
794 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800795 if ip in self.host_infos.table:
796 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
797
798 def HandleHostLogPing(self, ip):
799 """Returns a complete log of events for host in JSON format."""
800 if ip == 'all':
801 return json.dumps(
802 dict([(key, self.host_infos.table[key].log)
803 for key in self.host_infos.table]))
804 if ip in self.host_infos.table:
805 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700806
807 def HandleSetUpdatePing(self, ip, label):
808 """Sets forced_update_label for a given host."""
809 assert ip, 'No ip provided.'
810 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800811 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label