blob: cbe0a73f83093b4ce0f47069ff74c1abca2bd938 [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
Jay Srinivasan9a1c4572012-03-16 19:16:58 -0700301 ChromeOSVersion="9999.0.0"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700302 codebase="%s"
303 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700304 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700305 needsadmin="false"
306 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700307 IsDelta="%s"
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800308 status="ok"
309 %s/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000310 </app>
311 </gupdate>
312 """
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800313 extra_attributes = []
314 if self.critical_update:
315 # The date string looks like '20111115' (2011-11-15). As of writing,
316 # there's no particular format for the deadline value that the
317 # client expects -- it's just empty vs. non-empty.
318 date_str = datetime.date.today().strftime('%Y%m%d')
319 extra_attributes.append('deadline="%s"' % date_str)
320 xml = payload % (self._GetSecondsSinceMidnight(),
321 self.app_id, url, hash, sha256, size, delta,
322 ' '.join(extra_attributes))
323 _LogMessage('Generated update payload: %s' % xml)
324 return xml
rtc@google.comded22402009-10-26 22:36:21 +0000325
rtc@google.com21a5ca32009-11-04 18:23:23 +0000326 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700327 """Returns a payload to the client corresponding to no update."""
Darin Petkov845f1172011-01-05 14:45:24 -0800328 payload = """<?xml version="1.0" encoding="UTF-8"?>
329 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
330 <daystart elapsed_seconds="%s"/>
331 <app appid="{%s}" status="ok">
332 <ping status="ok"/>
333 <updatecheck status="noupdate"/>
334 </app>
335 </gupdate>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000336 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700337 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000338
Don Garrettf90edf02010-11-16 17:36:14 -0800339 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700340 """Generates an update gz given a full path to an image.
341
342 Args:
343 image_path: Full path to image.
344 Returns:
345 Path to created update_payload or None on error.
346 """
Don Garrettfff4c322010-11-19 13:37:12 -0800347 update_path = os.path.join(output_dir, UPDATE_FILE)
Chris Sosa7c931362010-10-11 19:49:01 -0700348 _LogMessage('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700349
Chris Sosa0f1ec842011-02-14 16:33:22 -0800350 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700351 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-14 16:33:22 -0800352 '--image="%s"' % image_path,
353 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800354 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700355
Chris Sosa0f1ec842011-02-14 16:33:22 -0800356 if src_image: update_command.append('--src_image="%s"' % src_image)
357 if not self.vm: update_command.append('--patch_kernel')
358 if self.private_key: update_command.append('--private_key="%s"' %
359 self.private_key)
360
361 update_string = ' '.join(update_command)
362 _LogMessage('Running ' + update_string)
363 if os.system(update_string) != 0:
Chris Sosa417e55d2011-01-25 16:40:48 -0800364 _LogMessage('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700365 return None
366
Don Garrettfff4c322010-11-19 13:37:12 -0800367 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700368
Don Garrettf90edf02010-11-16 17:36:14 -0800369 def GenerateStatefulFile(self, image_path, output_dir):
370 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700371
372 Args:
373 image_path: Full path to image.
374 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800375 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800376 Raises:
377 A subprocess exception if the update generator fails to generate a
378 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700379 """
Don Garrettfff4c322010-11-19 13:37:12 -0800380 output_gz = os.path.join(output_dir, STATEFUL_FILE)
Chris Sosa908fd6f2010-11-10 17:31:18 -0800381 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700382 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-10 17:31:18 -0800383 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800384 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800385 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800386 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700387
Don Garrettf90edf02010-11-16 17:36:14 -0800388 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
389 """Find directory to store a cached update.
390
Chris Sosa744e1472011-09-07 19:32:50 -0700391 Given one, or two images for an update, this finds which
392 cache directory should hold the update files, even if they don't exist
393 yet. The directory will be inside static_image_dir, and of the form:
Don Garrettf90edf02010-11-16 17:36:14 -0800394
Chris Sosa744e1472011-09-07 19:32:50 -0700395 Non-delta updates:
396 CACHE_DIR/12345678
397 Delta updates:
398 CACHE_DIR/12345678_12345678
Don Garrettf90edf02010-11-16 17:36:14 -0800399
Chris Sosa744e1472011-09-07 19:32:50 -0700400 If self.private_key -- Signed updates:
401 CACHE_DIR/from_above+12345678
402 """
403 sub_dir = self._GetMd5(dest_image)
404 if src_image:
405 sub_dir = '%s_%s' % (self._GetMd5(src_image), sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800406
Chris Sosa744e1472011-09-07 19:32:50 -0700407 if self.private_key:
408 sub_dir = '%s+%s' % (sub_dir, self._GetMd5(self.private_key))
409
Chris Sosa9fba7562012-01-31 10:15:47 -0800410 if not self.vm:
411 sub_dir = '%s+patched_kernel' % sub_dir
412
Chris Sosa744e1472011-09-07 19:32:50 -0700413 return os.path.join(CACHE_DIR, sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800414
Don Garrettfff4c322010-11-19 13:37:12 -0800415 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800416 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700417
Chris Sosade91f672010-11-16 10:05:44 -0800418 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800419 src_image: image we are updating from (Null/empty for non-delta)
420 image_path: full path to the image.
421 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800422 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800423 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800424 """
Don Garrettf90edf02010-11-16 17:36:14 -0800425 update_file = None
426 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700427
Don Garrettf90edf02010-11-16 17:36:14 -0800428 # Actually do the generation
429 _LogMessage('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800430 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800431 image_path,
432 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000433
Don Garrettf90edf02010-11-16 17:36:14 -0800434 if update_file:
435 stateful_update_file = self.GenerateStatefulFile(image_path,
436 output_dir)
437
438 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800439 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800440 else:
441 _LogMessage('Failed to generate update.')
442 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800443
444 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
445 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000446
Chris Sosa0356d3b2010-09-16 15:46:22 -0700447 Args:
448 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700449 static_image_dir: the directory to move images to after generating.
450 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800451 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800452 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700453 """
Don Garrettf90edf02010-11-16 17:36:14 -0800454 _LogMessage('Generating update for src %s image %s' % (self.src_image,
455 image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700456
Chris Sosa417e55d2011-01-25 16:40:48 -0800457 # If it was pregenerated_path, don't regenerate
458 if self.pregenerated_path:
459 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800460
Don Garrettf90edf02010-11-16 17:36:14 -0800461 # Which sub_dir of static_image_dir should hold our cached update image
462 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
463 _LogMessage('Caching in sub_dir "%s"' % cache_sub_dir)
464
Chris Sosa417e55d2011-01-25 16:40:48 -0800465 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
466
Don Garrettf90edf02010-11-16 17:36:14 -0800467 # The cached payloads exist in a cache dir
468 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800469 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800470 cache_stateful_payload = os.path.join(static_image_dir,
471 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800472 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800473
Chris Sosa417e55d2011-01-25 16:40:48 -0800474 # Check to see if this cache directory is valid.
475 if not os.path.exists(cache_update_payload) or not os.path.exists(
476 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800477 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800478 # Clean up stale state.
479 os.system('rm -rf "%s"' % full_cache_dir)
480 os.makedirs(full_cache_dir)
481 return_path = self.GenerateUpdateImage(image_path,
482 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800483
Chris Sosa417e55d2011-01-25 16:40:48 -0800484 # Clean up cache dir since it's not valid.
485 if not return_path:
486 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800487 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800488
489 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800490
Chris Sosa08d55a22011-01-19 16:08:02 -0800491 # Generation complete, copy if requested.
492 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800493 # The final results exist directly in static
494 update_payload = os.path.join(static_image_dir,
495 UPDATE_FILE)
496 stateful_payload = os.path.join(static_image_dir,
497 STATEFUL_FILE)
Chris Sosa08d55a22011-01-19 16:08:02 -0800498 self._Copy(cache_update_payload, update_payload)
499 self._Copy(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800500 return UPDATE_FILE
501 else:
502 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700503
504 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800505 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700506 """Generates an update using the latest image that has been built.
507
508 This will only generate an update if the newest update is newer than that
509 on the client or client_version is 'ForcedUpdate'.
510
511 Args:
512 board_id: Name of the board.
513 client_version: Current version of the client or 'ForcedUpdate'
514 static_image_dir: the directory to move images to after generating.
515 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800516 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700517 """
518 latest_image_dir = self._GetLatestImageDir(board_id)
519 latest_version = self._GetVersionFromDir(latest_image_dir)
520 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
521
Chris Sosa7c931362010-10-11 19:49:01 -0700522 _LogMessage('Preparing to generate update from latest built image %s.' %
Chris Sosa0356d3b2010-09-16 15:46:22 -0700523 latest_image_path)
524
525 # Check to see whether or not we should update.
526 if client_version != 'ForcedUpdate' and not self._CanUpdate(
527 client_version, latest_version):
Chris Sosa7c931362010-10-11 19:49:01 -0700528 _LogMessage('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800529 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700530
Don Garrettf90edf02010-11-16 17:36:14 -0800531 return self.GenerateUpdateImageWithCache(latest_image_path,
532 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700533
Andrew de los Reyes52620802010-04-12 13:40:07 -0700534 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
535 """Imports a factory-floor server configuration file. The file should
536 be in this format:
537 config = [
538 {
539 'qual_ids': set([1, 2, 3, "x86-generic"]),
540 'factory_image': 'generic-factory.gz',
541 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
542 'release_image': 'generic-release.gz',
543 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
544 'oempartitionimg_image': 'generic-oem.gz',
545 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700546 'efipartitionimg_image': 'generic-efi.gz',
547 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700548 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800549 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800550 'firmware_image': 'generic-firmware.gz',
551 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700552 },
553 {
554 'qual_ids': set([6]),
555 'factory_image': '6-factory.gz',
556 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
557 'release_image': '6-release.gz',
558 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
559 'oempartitionimg_image': '6-oem.gz',
560 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700561 'efipartitionimg_image': '6-efi.gz',
562 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700563 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800564 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800565 'firmware_image': '6-firmware.gz',
566 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700567 },
568 ]
569 The server will look for the files by name in the static files
570 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700571
Andrew de los Reyes52620802010-04-12 13:40:07 -0700572 If validate_checksums is True, validates checksums and exits. If
573 a checksum mismatch is found, it's printed to the screen.
574 """
575 f = open(filename, 'r')
576 output = {}
577 exec(f.read(), output)
578 self.factory_config = output['config']
579 success = True
580 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800581 for key in stanza.copy().iterkeys():
582 suffix = '_image'
583 if key.endswith(suffix):
584 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700585 stanza[kind + '_size'] = self._GetSize(os.path.join(
586 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800587 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700588 factory_checksum = self._GetHash(os.path.join(self.static_dir,
589 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800590 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700591 print ('Error: checksum mismatch for %s. Expected "%s" but file '
592 'has checksum "%s".' % (stanza[kind + '_image'],
593 stanza[kind + '_checksum'],
594 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800595 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700596
Andrew de los Reyes52620802010-04-12 13:40:07 -0700597 if validate_checksums:
598 if success is False:
599 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700600
Andrew de los Reyes52620802010-04-12 13:40:07 -0700601 print 'Config file looks good.'
602
603 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700604 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700605 for stanza in self.factory_config:
606 if board_id not in stanza['qual_ids']:
607 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700608 if kind + '_image' not in stanza:
609 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700610 return (stanza[kind + '_image'],
611 stanza[kind + '_checksum'],
612 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700613 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000614
Chris Sosa7c931362010-10-11 19:49:01 -0700615 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700616 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
617 if filename is None:
Chris Sosa7c931362010-10-11 19:49:01 -0700618 _LogMessage('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700619 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700620 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700621 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa7c931362010-10-11 19:49:01 -0700622 _LogMessage('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700623 # Factory install is using memento updater which is using the sha-1 hash so
624 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700625 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700626
Chris Sosa151643e2010-10-28 14:40:57 -0700627 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
628 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800629 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800630
Don Garrettf90edf02010-11-16 17:36:14 -0800631 Returns:
632 file name relative to static_image_dir on success.
633 """
Dale Curtis723ec472010-11-30 14:06:47 -0800634 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
635 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
636
Don Garrett0c880e22010-11-17 18:13:37 -0800637 if self.forced_payload:
638 # If the forced payload is not already in our static_image_dir,
639 # copy it there.
Don Garrettee25e552010-11-23 12:09:35 -0800640 src_path = os.path.abspath(self.forced_payload)
Don Garrettee25e552010-11-23 12:09:35 -0800641 src_stateful = os.path.join(os.path.dirname(src_path),
642 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800643
644 # Only copy the files if the source directory is different from dest.
645 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
646 self._Copy(src_path, dest_path)
647
648 # The stateful payload is optional.
649 if os.path.exists(src_stateful):
650 self._Copy(src_stateful, dest_stateful)
651 else:
652 _LogMessage('WARN: %s not found. Expected for dev and test builds.' %
653 STATEFUL_FILE)
654 if os.path.exists(dest_stateful):
655 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800656
Don Garrettfff4c322010-11-19 13:37:12 -0800657 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800658 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800659 return self.GenerateUpdateImageWithCache(
660 self.forced_image,
661 static_image_dir=static_image_dir)
662 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800663 # Warn if update or stateful files can't be found.
664 if not os.path.exists(dest_path):
665 _LogMessage('WARN: %s not found. Expected for dev and test builds.' %
666 UPDATE_FILE)
667
668 if not os.path.exists(dest_stateful):
669 _LogMessage('WARN: %s not found. Expected for dev and test builds.' %
670 STATEFUL_FILE)
671
672 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800673 else:
674 if board_id:
675 return self.GenerateLatestUpdateImage(board_id,
676 client_version,
677 static_image_dir)
678
Chris Sosa417e55d2011-01-25 16:40:48 -0800679 _LogMessage('Failed to genereate update. '
680 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800681 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700682
683 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800684 """Pre-generates an update and prints out the relative path it.
685
686 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800687 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700688 # Does not work with factory config.
689 assert(not self.factory_config)
690 _LogMessage('Pre-generating the update payload.')
691 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800692 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
693 self.board, '0.0.0.0', self.static_dir)
694 if pregenerated_update:
695 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
696
697 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700698
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700699 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700700 """Handles an update ping from an update client.
701
702 Args:
703 data: xml blob from client.
704 label: optional label for the update.
705 Returns:
706 Update payload message for client.
707 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700708 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 16:26:10 -0700709 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
710 # be in X-Forwarded-Host.
711 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
712 if x_forwarded_host:
713 self.hostname = 'http://' + x_forwarded_host
714 else:
715 self.hostname = cherrypy.request.base
716
Chris Sosa9841e1c2010-10-14 10:51:45 -0700717 if self.urlbase:
718 static_urlbase = self.urlbase
719 elif self.serve_only:
720 static_urlbase = '%s/static/archive' % self.hostname
721 else:
722 static_urlbase = '%s/static' % self.hostname
723
Don Garrett0ad09372010-12-06 16:20:30 -0800724 # If we have a proxy port, adjust the URL we instruct the client to
725 # use to go through the proxy.
726 if self.proxy_port:
727 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
728
Chris Sosa9841e1c2010-10-14 10:51:45 -0700729 _LogMessage('Using static url base %s' % static_urlbase)
730 _LogMessage('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700731
Chris Sosa9841e1c2010-10-14 10:51:45 -0700732 update_dom = minidom.parseString(data)
733 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700734
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700735 # Determine request IP, strip any IPv6 data for simplicity.
736 client_ip = cherrypy.request.remote.ip.split(':')[-1]
737
Gilad Arnold286a0062012-01-12 13:47:02 -0800738 # Obtain (or init) info object for this client.
739 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
740
741 # Initialize an empty dictionary for event attributes.
742 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700743
744 # Store event details in the host info dictionary for API usage.
745 event = root.getElementsByTagName('o:event')
746 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800747 event_result = int(event[0].getAttribute('eventresult'))
748 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700749 client_previous_version = (event[0].getAttribute('previousversion')
750 if event[0].hasAttribute('previousversion')
751 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800752 # Store attributes to legacy host info structure
753 curr_host_info.attrs['last_event_status'] = event_result
754 curr_host_info.attrs['last_event_type'] = event_type
755 # Add attributes to log message
756 log_message['event_result'] = event_result
757 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700758 if client_previous_version is not None:
759 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800760
761 # Get information about the requester.
762 query = root.getElementsByTagName('o:app')[0]
763 if query:
764 client_version = query.getAttribute('version')
765 channel = query.getAttribute('track')
766 board_id = (query.hasAttribute('board') and query.getAttribute('board')
767 or self._GetDefaultBoardID())
768 # Add attributes to log message
769 log_message['version'] = client_version
770 log_message['track'] = channel
771 log_message['board'] = board_id
772
773 # Log client's message
774 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700775
Chris Sosa0356d3b2010-09-16 15:46:22 -0700776 # We only generate update payloads for updatecheck requests.
777 update_check = root.getElementsByTagName('o:updatecheck')
778 if not update_check:
Chris Sosa7c931362010-10-11 19:49:01 -0700779 _LogMessage('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700780 # TODO(sosa): Generate correct non-updatecheck payload to better test
781 # update clients.
782 return self.GetNoUpdatePayload()
783
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700784 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800785 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700786
787 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 13:47:02 -0800788 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700789 if forced_update:
790 label = forced_update
791
Chris Sosa0356d3b2010-09-16 15:46:22 -0700792 # Separate logic as Factory requests have static url's that override
793 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700794 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700795 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700796 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700797 static_image_dir = self.static_dir
798 if label:
799 static_image_dir = os.path.join(static_image_dir, label)
800
Don Garrettf90edf02010-11-16 17:36:14 -0800801 payload_path = self.GenerateUpdatePayloadForNonFactory(board_id,
802 client_version,
803 static_image_dir)
804 if payload_path:
805 filename = os.path.join(static_image_dir, payload_path)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700806 hash = self._GetHash(filename)
807 sha256 = self._GetSHA256(filename)
808 size = self._GetSize(filename)
809 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700810 if label:
Don Garrettf90edf02010-11-16 17:36:14 -0800811 url = '%s/%s/%s' % (static_urlbase, label, payload_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700812 else:
Don Garrettf90edf02010-11-16 17:36:14 -0800813 url = '%s/%s' % (static_urlbase, payload_path)
Chris Sosa5d342a22010-09-28 16:54:41 -0700814
Chris Sosa7c931362010-10-11 19:49:01 -0700815 _LogMessage('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700816 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700817 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700818 return self.GetNoUpdatePayload()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700819
820 def HandleHostInfoPing(self, ip):
821 """Returns host info dictionary for the given IP in JSON format."""
822 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800823 if ip in self.host_infos.table:
824 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
825
826 def HandleHostLogPing(self, ip):
827 """Returns a complete log of events for host in JSON format."""
828 if ip == 'all':
829 return json.dumps(
830 dict([(key, self.host_infos.table[key].log)
831 for key in self.host_infos.table]))
832 if ip in self.host_infos.table:
833 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700834
835 def HandleSetUpdatePing(self, ip, label):
836 """Sets forced_update_label for a given host."""
837 assert ip, 'No ip provided.'
838 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800839 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label