blob: 591fe800eb6be6d8ab2a6fd8bd56af2936f56dba [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 Arnoldc65330c2012-09-20 15:17:48 -070017import log_util
Chris Sosa05491b12010-11-08 17:14:16 -080018
Gilad Arnoldc65330c2012-09-20 15:17:48 -070019
20# Module-local log function.
21def _Log(message, *args, **kwargs):
22 return log_util.LogWithTag('UPDATE', message, *args, **kwargs)
23
rtc@google.comded22402009-10-26 22:36:21 +000024
Chris Sosa417e55d2011-01-25 16:40:48 -080025UPDATE_FILE = 'update.gz'
26STATEFUL_FILE = 'stateful.tgz'
27CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 15:46:22 -070028
Don Garrett0ad09372010-12-06 16:20:30 -080029
30def _ChangeUrlPort(url, new_port):
31 """Return the URL passed in with a different port"""
32 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
33 host_port = netloc.split(':')
34
35 if len(host_port) == 1:
36 host_port.append(new_port)
37 else:
38 host_port[1] = new_port
39
40 print host_port
41 netloc = "%s:%s" % tuple(host_port)
42
43 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
44
45
Gilad Arnold286a0062012-01-12 13:47:02 -080046class HostInfo:
47 """Records information about an individual host.
48
49 Members:
50 attrs: Static attributes (legacy)
51 log: Complete log of recorded client entries
52 """
53
54 def __init__(self):
55 # A dictionary of current attributes pertaining to the host.
56 self.attrs = {}
57
58 # A list of pairs consisting of a timestamp and a dictionary of recorded
59 # attributes.
60 self.log = []
61
62 def __repr__(self):
63 return 'attrs=%s, log=%s' % (self.attrs, self.log)
64
65 def AddLogEntry(self, entry):
66 """Append a new log entry."""
67 # Append a timestamp.
68 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
69 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
70 # Add entry to hosts' message log.
71 self.log.append(entry)
72
73 def SetAttr(self, attr, value):
74 """Set an attribute value."""
75 self.attrs[attr] = value
76
77 def GetAttr(self, attr):
78 """Returns the value of an attribute."""
79 if attr in self.attrs:
80 return self.attrs[attr]
81
82 def PopAttr(self, attr, default):
83 """Returns and deletes a particular attribute."""
84 return self.attrs.pop(attr, default)
85
86
87class HostInfoTable:
88 """Records information about a set of hosts who engage in update activity.
89
90 Members:
91 table: Table of information on hosts.
92 """
93
94 def __init__(self):
95 # A dictionary of host information. Keys are normally IP addresses.
96 self.table = {}
97
98 def __repr__(self):
99 return '%s' % self.table
100
101 def GetInitHostInfo(self, host_id):
102 """Return a host's info object, or create a new one if none exists."""
103 return self.table.setdefault(host_id, HostInfo())
104
105 def GetHostInfo(self, host_id):
106 """Return an info object for given host, if such exists."""
107 if host_id in self.table:
108 return self.table[host_id]
109
110
rtc@google.com64244662009-11-12 00:52:08 +0000111class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700112 """Class that contains functionality that handles Chrome OS update pings.
113
114 Members:
Dale Curtis723ec472010-11-30 14:06:47 -0800115 serve_only: Serve only pre-built updates. static_dir must contain update.gz
116 and stateful.tgz.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700117 factory_config: Path to the factory config file if handling factory
118 requests.
119 use_test_image: Use chromiumos_test_image.bin rather than the standard.
120 static_url_base: base URL, other than devserver, for update images.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700121 forced_image: Path to an image to use for all updates.
Chris Sosa08d55a22011-01-19 16:08:02 -0800122 forced_payload: Path to pre-generated payload to serve.
123 port: port to host devserver
124 proxy_port: port of local proxy to tell client to connect to you through.
125 src_image: If specified, creates a delta payload from this image.
126 vm: Set for VM images (doesn't patch kernel)
127 board: board for the image. Needed for pre-generating of updates.
128 copy_to_static_root: Copies images generated from the cache to
129 ~/static.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700130 """
rtc@google.comded22402009-10-26 22:36:21 +0000131
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700132 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 14:15:23 -0700133 factory_config_path=None,
Don Garrett0c880e22010-11-17 18:13:37 -0800134 forced_image=None, forced_payload=None,
Don Garrett0ad09372010-12-06 16:20:30 -0800135 port=8080, proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800136 copy_to_static_root=True, private_key=None,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800137 critical_update=False,
Chris Sosae67b78f2010-11-04 17:33:16 -0700138 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700139 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700140 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -0700141 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700142 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -0700143 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700144 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700145 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700146 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700147
Chris Sosa0356d3b2010-09-16 15:46:22 -0700148 self.forced_image = forced_image
Don Garrett0c880e22010-11-17 18:13:37 -0800149 self.forced_payload = forced_payload
Chris Sosa62f720b2010-10-26 21:39:48 -0700150 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800151 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-28 23:42:37 -0700152 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -0700153 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800154 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800155 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800156 self.critical_update = critical_update
Don Garrettfff4c322010-11-19 13:37:12 -0800157
Chris Sosa417e55d2011-01-25 16:40:48 -0800158 # Path to pre-generated file.
159 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700160
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700161 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800162 # information about a given host. A host is identified by its IP address.
163 # The info stored for each host includes a complete log of events for this
164 # host, as well as a dictionary of current attributes derived from events.
165 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700166
Chris Sosa0356d3b2010-09-16 15:46:22 -0700167 def _GetSecondsSinceMidnight(self):
168 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700169 now = time.localtime()
170 return now[3] * 3600 + now[4] * 60 + now[5]
171
Chris Sosa0356d3b2010-09-16 15:46:22 -0700172 def _GetDefaultBoardID(self):
173 """Returns the default board id stored in .default_board."""
174 board_file = '%s/.default_board' % (self.scripts_dir)
175 try:
176 return open(board_file).read()
177 except IOError:
178 return 'x86-generic'
179
180 def _GetLatestImageDir(self, board_id):
181 """Returns the latest image dir based on shell script."""
182 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
183 return os.popen(cmd).read().strip()
184
185 def _GetVersionFromDir(self, image_dir):
186 """Returns the version of the image based on the name of the directory."""
187 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700188 parts = latest_version.split('-')
189 if len(parts) == 2:
190 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
191 # TODO(derat): Remove the code for old-style versions after 20120101.
192 return parts[0]
193 else:
194 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
195 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700196
197 def _CanUpdate(self, client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800198 """Returns true if the latest_version is greater than the client_version.
199 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700200 _Log('client version %s latest version %s'
201 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700202
203 client_tokens = client_version.replace('_', '').split('.')
204 # If the client has an old four-token version like "0.16.892.0", drop the
205 # first two tokens -- we use versions like "892.0.0" now.
206 # TODO(derat): Remove the code for old-style versions after 20120101.
207 if len(client_tokens) == 4:
208 client_tokens = client_tokens[2:]
209
210 latest_tokens = latest_version.replace('_', '').split('.')
211 if len(latest_tokens) == 4:
212 latest_tokens = latest_tokens[2:]
213
214 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700215 if int(latest_tokens[i]) == int(client_tokens[i]):
216 continue
217 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700218
219 # Favor four-token new-style versions on the server over old-style versions
220 # on the client if everything else matches.
221 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700222
Chris Sosa0356d3b2010-09-16 15:46:22 -0700223 def _UnpackZip(self, image_dir):
224 """Unpacks an image.zip into a given directory."""
225 image = os.path.join(image_dir, self._GetImageName())
226 if os.path.exists(image):
227 return True
228 else:
229 # -n, never clobber an existing file, in case we get invoked
230 # simultaneously by multiple request handlers. This means that
231 # we're assuming each image.zip file lives in a versioned
232 # directory (a la Buildbot).
233 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
234
235 def _GetImageName(self):
236 """Returns the name of the image that should be used."""
237 if self.use_test_image:
238 image_name = 'chromiumos_test_image.bin'
239 else:
240 image_name = 'chromiumos_image.bin'
241 return image_name
242
Chris Sosa0356d3b2010-09-16 15:46:22 -0700243 def _GetSize(self, update_path):
244 """Returns the size of the file given."""
245 return os.path.getsize(update_path)
246
247 def _GetHash(self, update_path):
248 """Returns the sha1 of the file given."""
249 cmd = ('cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';'
250 % update_path)
251 return os.popen(cmd).read().rstrip()
252
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700253 def _IsDeltaFormatFile(self, filename):
254 try:
255 file_handle = open(filename, 'r')
256 delta_magic = 'CrAU'
257 magic = file_handle.read(len(delta_magic))
258 return magic == delta_magic
259 except Exception:
260 return False
261
Darin Petkov91436cb2010-09-28 08:52:17 -0700262 # TODO(petkov): Consider optimizing getting both SHA-1 and SHA-256 so that
263 # it takes advantage of reduced I/O and multiple processors. Something like:
264 # % tee < FILE > /dev/null \
265 # >( openssl dgst -sha256 -binary | openssl base64 ) \
266 # >( openssl sha1 -binary | openssl base64 )
267 def _GetSHA256(self, update_path):
268 """Returns the sha256 of the file given."""
269 cmd = ('cat %s | openssl dgst -sha256 -binary | openssl base64' %
270 update_path)
271 return os.popen(cmd).read().rstrip()
272
Don Garrettf90edf02010-11-16 17:36:14 -0800273 def _GetMd5(self, update_path):
274 """Returns the md5 checksum of the file given."""
275 cmd = ("md5sum %s | awk '{print $1}'" % update_path)
276 return os.popen(cmd).read().rstrip()
277
Don Garrett0c880e22010-11-17 18:13:37 -0800278 def _Copy(self, source, dest):
279 """Copies a file from dest to source (if different)"""
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700280 _Log('Copy File %s -> %s' % (source, dest))
Don Garrett0c880e22010-11-17 18:13:37 -0800281 if os.path.lexists(dest):
Don Garrettf90edf02010-11-16 17:36:14 -0800282 os.remove(dest)
Don Garrett0c880e22010-11-17 18:13:37 -0800283 shutil.copy(source, dest)
Don Garrettf90edf02010-11-16 17:36:14 -0800284
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700285 def GetUpdatePayload(self, hash, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700286 """Returns a payload to the client corresponding to a new update.
287
288 Args:
289 hash: hash of update blob
Darin Petkov91436cb2010-09-28 08:52:17 -0700290 sha256: SHA-256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700291 size: size of update blob
292 url: where to find update blob
293 Returns:
294 Xml string to be passed back to client.
295 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700296 delta = 'false'
297 if is_delta_format:
298 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000299 payload = """<?xml version="1.0" encoding="UTF-8"?>
300 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700301 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000302 <app appid="{%s}" status="ok">
303 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700304 <updatecheck
Jay Srinivasan9a1c4572012-03-16 19:16:58 -0700305 ChromeOSVersion="9999.0.0"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700306 codebase="%s"
307 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700308 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700309 needsadmin="false"
310 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700311 IsDelta="%s"
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800312 status="ok"
313 %s/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000314 </app>
315 </gupdate>
316 """
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800317 extra_attributes = []
318 if self.critical_update:
319 # The date string looks like '20111115' (2011-11-15). As of writing,
320 # there's no particular format for the deadline value that the
321 # client expects -- it's just empty vs. non-empty.
322 date_str = datetime.date.today().strftime('%Y%m%d')
323 extra_attributes.append('deadline="%s"' % date_str)
324 xml = payload % (self._GetSecondsSinceMidnight(),
325 self.app_id, url, hash, sha256, size, delta,
326 ' '.join(extra_attributes))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700327 _Log('Generated update payload: %s' % xml)
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800328 return xml
rtc@google.comded22402009-10-26 22:36:21 +0000329
rtc@google.com21a5ca32009-11-04 18:23:23 +0000330 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700331 """Returns a payload to the client corresponding to no update."""
Darin Petkov845f1172011-01-05 14:45:24 -0800332 payload = """<?xml version="1.0" encoding="UTF-8"?>
333 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
334 <daystart elapsed_seconds="%s"/>
335 <app appid="{%s}" status="ok">
336 <ping status="ok"/>
337 <updatecheck status="noupdate"/>
338 </app>
339 </gupdate>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000340 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700341 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000342
Don Garrettf90edf02010-11-16 17:36:14 -0800343 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700344 """Generates an update gz given a full path to an image.
345
346 Args:
347 image_path: Full path to image.
348 Returns:
349 Path to created update_payload or None on error.
350 """
Don Garrettfff4c322010-11-19 13:37:12 -0800351 update_path = os.path.join(output_dir, UPDATE_FILE)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700352 _Log('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700353
Chris Sosa0f1ec842011-02-14 16:33:22 -0800354 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700355 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-14 16:33:22 -0800356 '--image="%s"' % image_path,
357 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800358 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700359
Chris Sosa0f1ec842011-02-14 16:33:22 -0800360 if src_image: update_command.append('--src_image="%s"' % src_image)
361 if not self.vm: update_command.append('--patch_kernel')
362 if self.private_key: update_command.append('--private_key="%s"' %
363 self.private_key)
364
365 update_string = ' '.join(update_command)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700366 _Log('Running ' + update_string)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800367 if os.system(update_string) != 0:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700368 _Log('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700369 return None
370
Don Garrettfff4c322010-11-19 13:37:12 -0800371 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700372
Don Garrettf90edf02010-11-16 17:36:14 -0800373 def GenerateStatefulFile(self, image_path, output_dir):
374 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700375
376 Args:
377 image_path: Full path to image.
378 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800379 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800380 Raises:
381 A subprocess exception if the update generator fails to generate a
382 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700383 """
Don Garrettfff4c322010-11-19 13:37:12 -0800384 output_gz = os.path.join(output_dir, STATEFUL_FILE)
Chris Sosa908fd6f2010-11-10 17:31:18 -0800385 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700386 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-10 17:31:18 -0800387 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800388 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800389 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800390 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700391
Don Garrettf90edf02010-11-16 17:36:14 -0800392 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
393 """Find directory to store a cached update.
394
Chris Sosa744e1472011-09-07 19:32:50 -0700395 Given one, or two images for an update, this finds which
396 cache directory should hold the update files, even if they don't exist
397 yet. The directory will be inside static_image_dir, and of the form:
Don Garrettf90edf02010-11-16 17:36:14 -0800398
Chris Sosa744e1472011-09-07 19:32:50 -0700399 Non-delta updates:
400 CACHE_DIR/12345678
401 Delta updates:
402 CACHE_DIR/12345678_12345678
Don Garrettf90edf02010-11-16 17:36:14 -0800403
Chris Sosa744e1472011-09-07 19:32:50 -0700404 If self.private_key -- Signed updates:
405 CACHE_DIR/from_above+12345678
406 """
407 sub_dir = self._GetMd5(dest_image)
408 if src_image:
409 sub_dir = '%s_%s' % (self._GetMd5(src_image), sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800410
Chris Sosa744e1472011-09-07 19:32:50 -0700411 if self.private_key:
412 sub_dir = '%s+%s' % (sub_dir, self._GetMd5(self.private_key))
413
Chris Sosa9fba7562012-01-31 10:15:47 -0800414 if not self.vm:
415 sub_dir = '%s+patched_kernel' % sub_dir
416
Chris Sosa744e1472011-09-07 19:32:50 -0700417 return os.path.join(CACHE_DIR, sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800418
Don Garrettfff4c322010-11-19 13:37:12 -0800419 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800420 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700421
Chris Sosade91f672010-11-16 10:05:44 -0800422 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800423 src_image: image we are updating from (Null/empty for non-delta)
424 image_path: full path to the image.
425 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800426 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800427 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800428 """
Don Garrettf90edf02010-11-16 17:36:14 -0800429 update_file = None
430 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700431
Don Garrettf90edf02010-11-16 17:36:14 -0800432 # Actually do the generation
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700433 _Log('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800434 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800435 image_path,
436 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000437
Don Garrettf90edf02010-11-16 17:36:14 -0800438 if update_file:
439 stateful_update_file = self.GenerateStatefulFile(image_path,
440 output_dir)
441
442 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800443 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800444 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700445 _Log('Failed to generate update.')
Chris Sosa417e55d2011-01-25 16:40:48 -0800446 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800447
448 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
449 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000450
Chris Sosa0356d3b2010-09-16 15:46:22 -0700451 Args:
452 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700453 static_image_dir: the directory to move images to after generating.
454 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800455 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800456 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700457 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700458 _Log('Generating update for src %s image %s' % (self.src_image, image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700459
Chris Sosa417e55d2011-01-25 16:40:48 -0800460 # If it was pregenerated_path, don't regenerate
461 if self.pregenerated_path:
462 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800463
Don Garrettf90edf02010-11-16 17:36:14 -0800464 # Which sub_dir of static_image_dir should hold our cached update image
465 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700466 _Log('Caching in sub_dir "%s"' % cache_sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800467
Chris Sosa417e55d2011-01-25 16:40:48 -0800468 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
469
Don Garrettf90edf02010-11-16 17:36:14 -0800470 # The cached payloads exist in a cache dir
471 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800472 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800473 cache_stateful_payload = os.path.join(static_image_dir,
474 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800475 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800476
Chris Sosa417e55d2011-01-25 16:40:48 -0800477 # Check to see if this cache directory is valid.
478 if not os.path.exists(cache_update_payload) or not os.path.exists(
479 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800480 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800481 # Clean up stale state.
482 os.system('rm -rf "%s"' % full_cache_dir)
483 os.makedirs(full_cache_dir)
484 return_path = self.GenerateUpdateImage(image_path,
485 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800486
Chris Sosa417e55d2011-01-25 16:40:48 -0800487 # Clean up cache dir since it's not valid.
488 if not return_path:
489 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800490 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800491
492 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800493
Chris Sosa08d55a22011-01-19 16:08:02 -0800494 # Generation complete, copy if requested.
495 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800496 # The final results exist directly in static
497 update_payload = os.path.join(static_image_dir,
498 UPDATE_FILE)
499 stateful_payload = os.path.join(static_image_dir,
500 STATEFUL_FILE)
Chris Sosa08d55a22011-01-19 16:08:02 -0800501 self._Copy(cache_update_payload, update_payload)
502 self._Copy(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800503 return UPDATE_FILE
504 else:
505 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700506
507 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800508 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700509 """Generates an update using the latest image that has been built.
510
511 This will only generate an update if the newest update is newer than that
512 on the client or client_version is 'ForcedUpdate'.
513
514 Args:
515 board_id: Name of the board.
516 client_version: Current version of the client or 'ForcedUpdate'
517 static_image_dir: the directory to move images to after generating.
518 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800519 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700520 """
521 latest_image_dir = self._GetLatestImageDir(board_id)
522 latest_version = self._GetVersionFromDir(latest_image_dir)
523 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
524
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700525 _Log('Preparing to generate update from latest built image %s.' %
526 latest_image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700527
528 # Check to see whether or not we should update.
529 if client_version != 'ForcedUpdate' and not self._CanUpdate(
530 client_version, latest_version):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700531 _Log('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800532 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700533
Don Garrettf90edf02010-11-16 17:36:14 -0800534 return self.GenerateUpdateImageWithCache(latest_image_path,
535 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700536
Andrew de los Reyes52620802010-04-12 13:40:07 -0700537 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
538 """Imports a factory-floor server configuration file. The file should
539 be in this format:
540 config = [
541 {
542 'qual_ids': set([1, 2, 3, "x86-generic"]),
543 'factory_image': 'generic-factory.gz',
544 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
545 'release_image': 'generic-release.gz',
546 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
547 'oempartitionimg_image': 'generic-oem.gz',
548 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700549 'efipartitionimg_image': 'generic-efi.gz',
550 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700551 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800552 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800553 'firmware_image': 'generic-firmware.gz',
554 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700555 },
556 {
557 'qual_ids': set([6]),
558 'factory_image': '6-factory.gz',
559 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
560 'release_image': '6-release.gz',
561 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
562 'oempartitionimg_image': '6-oem.gz',
563 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700564 'efipartitionimg_image': '6-efi.gz',
565 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700566 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800567 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800568 'firmware_image': '6-firmware.gz',
569 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700570 },
571 ]
572 The server will look for the files by name in the static files
573 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700574
Andrew de los Reyes52620802010-04-12 13:40:07 -0700575 If validate_checksums is True, validates checksums and exits. If
576 a checksum mismatch is found, it's printed to the screen.
577 """
578 f = open(filename, 'r')
579 output = {}
580 exec(f.read(), output)
581 self.factory_config = output['config']
582 success = True
583 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800584 for key in stanza.copy().iterkeys():
585 suffix = '_image'
586 if key.endswith(suffix):
587 kind = key[:-len(suffix)]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700588 stanza[kind + '_size'] = self._GetSize(os.path.join(
589 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800590 if validate_checksums:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700591 factory_checksum = self._GetHash(os.path.join(self.static_dir,
592 stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800593 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700594 print ('Error: checksum mismatch for %s. Expected "%s" but file '
595 'has checksum "%s".' % (stanza[kind + '_image'],
596 stanza[kind + '_checksum'],
597 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800598 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700599
Andrew de los Reyes52620802010-04-12 13:40:07 -0700600 if validate_checksums:
601 if success is False:
602 raise Exception('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700603
Andrew de los Reyes52620802010-04-12 13:40:07 -0700604 print 'Config file looks good.'
605
606 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700607 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700608 for stanza in self.factory_config:
609 if board_id not in stanza['qual_ids']:
610 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700611 if kind + '_image' not in stanza:
612 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700613 return (stanza[kind + '_image'],
614 stanza[kind + '_checksum'],
615 stanza[kind + '_size'])
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700616 return (None, None, None)
rtc@google.comded22402009-10-26 22:36:21 +0000617
Chris Sosa7c931362010-10-11 19:49:01 -0700618 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700619 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
620 if filename is None:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700621 _Log('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700622 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700623 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700624 is_delta_format = self._IsDeltaFormatFile(filename)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700625 _Log('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700626 # Factory install is using memento updater which is using the sha-1 hash so
627 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700628 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700629
Chris Sosa151643e2010-10-28 14:40:57 -0700630 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
631 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800632 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800633
Don Garrettf90edf02010-11-16 17:36:14 -0800634 Returns:
635 file name relative to static_image_dir on success.
636 """
Dale Curtis723ec472010-11-30 14:06:47 -0800637 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
638 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
639
Don Garrett0c880e22010-11-17 18:13:37 -0800640 if self.forced_payload:
641 # If the forced payload is not already in our static_image_dir,
642 # copy it there.
Don Garrettee25e552010-11-23 12:09:35 -0800643 src_path = os.path.abspath(self.forced_payload)
Don Garrettee25e552010-11-23 12:09:35 -0800644 src_stateful = os.path.join(os.path.dirname(src_path),
645 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800646
647 # Only copy the files if the source directory is different from dest.
648 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
649 self._Copy(src_path, dest_path)
650
651 # The stateful payload is optional.
652 if os.path.exists(src_stateful):
653 self._Copy(src_stateful, dest_stateful)
654 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700655 _Log('WARN: %s not found. Expected for dev and test builds.' %
656 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800657 if os.path.exists(dest_stateful):
658 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800659
Don Garrettfff4c322010-11-19 13:37:12 -0800660 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800661 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800662 return self.GenerateUpdateImageWithCache(
663 self.forced_image,
664 static_image_dir=static_image_dir)
665 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800666 # Warn if update or stateful files can't be found.
667 if not os.path.exists(dest_path):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700668 _Log('WARN: %s not found. Expected for dev and test builds.' %
669 UPDATE_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800670
671 if not os.path.exists(dest_stateful):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700672 _Log('WARN: %s not found. Expected for dev and test builds.' %
673 STATEFUL_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800674
675 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800676 else:
677 if board_id:
678 return self.GenerateLatestUpdateImage(board_id,
679 client_version,
680 static_image_dir)
681
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700682 _Log('Failed to genereate update. '
683 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800684 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700685
686 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800687 """Pre-generates an update and prints out the relative path it.
688
689 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800690 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700691 # Does not work with factory config.
692 assert(not self.factory_config)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700693 _Log('Pre-generating the update payload.')
Chris Sosa2c048f12010-10-27 16:05:27 -0700694 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800695 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
696 self.board, '0.0.0.0', self.static_dir)
697 if pregenerated_update:
698 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
699
700 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700701
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700702 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700703 """Handles an update ping from an update client.
704
705 Args:
706 data: xml blob from client.
707 label: optional label for the update.
708 Returns:
709 Update payload message for client.
710 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700711 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 16:26:10 -0700712 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
713 # be in X-Forwarded-Host.
714 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
715 if x_forwarded_host:
716 self.hostname = 'http://' + x_forwarded_host
717 else:
718 self.hostname = cherrypy.request.base
719
Chris Sosa9841e1c2010-10-14 10:51:45 -0700720 if self.urlbase:
721 static_urlbase = self.urlbase
722 elif self.serve_only:
723 static_urlbase = '%s/static/archive' % self.hostname
724 else:
725 static_urlbase = '%s/static' % self.hostname
726
Don Garrett0ad09372010-12-06 16:20:30 -0800727 # If we have a proxy port, adjust the URL we instruct the client to
728 # use to go through the proxy.
729 if self.proxy_port:
730 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
731
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700732 _Log('Using static url base %s' % static_urlbase)
733 _Log('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700734
Chris Sosa9841e1c2010-10-14 10:51:45 -0700735 update_dom = minidom.parseString(data)
736 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700737
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700738 # Determine request IP, strip any IPv6 data for simplicity.
739 client_ip = cherrypy.request.remote.ip.split(':')[-1]
740
Gilad Arnold286a0062012-01-12 13:47:02 -0800741 # Obtain (or init) info object for this client.
742 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
743
744 # Initialize an empty dictionary for event attributes.
745 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700746
747 # Store event details in the host info dictionary for API usage.
748 event = root.getElementsByTagName('o:event')
749 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800750 event_result = int(event[0].getAttribute('eventresult'))
751 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700752 client_previous_version = (event[0].getAttribute('previousversion')
753 if event[0].hasAttribute('previousversion')
754 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800755 # Store attributes to legacy host info structure
756 curr_host_info.attrs['last_event_status'] = event_result
757 curr_host_info.attrs['last_event_type'] = event_type
758 # Add attributes to log message
759 log_message['event_result'] = event_result
760 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700761 if client_previous_version is not None:
762 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800763
764 # Get information about the requester.
765 query = root.getElementsByTagName('o:app')[0]
766 if query:
767 client_version = query.getAttribute('version')
768 channel = query.getAttribute('track')
769 board_id = (query.hasAttribute('board') and query.getAttribute('board')
770 or self._GetDefaultBoardID())
771 # Add attributes to log message
772 log_message['version'] = client_version
773 log_message['track'] = channel
774 log_message['board'] = board_id
775
776 # Log client's message
777 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700778
Chris Sosa0356d3b2010-09-16 15:46:22 -0700779 # We only generate update payloads for updatecheck requests.
780 update_check = root.getElementsByTagName('o:updatecheck')
781 if not update_check:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700782 _Log('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700783 # TODO(sosa): Generate correct non-updatecheck payload to better test
784 # update clients.
785 return self.GetNoUpdatePayload()
786
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700787 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800788 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700789
790 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 13:47:02 -0800791 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700792 if forced_update:
793 label = forced_update
794
Chris Sosa0356d3b2010-09-16 15:46:22 -0700795 # Separate logic as Factory requests have static url's that override
796 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700797 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700798 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700799 else:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700800 static_image_dir = self.static_dir
801 if label:
802 static_image_dir = os.path.join(static_image_dir, label)
803
Don Garrettf90edf02010-11-16 17:36:14 -0800804 payload_path = self.GenerateUpdatePayloadForNonFactory(board_id,
805 client_version,
806 static_image_dir)
807 if payload_path:
808 filename = os.path.join(static_image_dir, payload_path)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700809 hash = self._GetHash(filename)
810 sha256 = self._GetSHA256(filename)
811 size = self._GetSize(filename)
812 is_delta_format = self._IsDeltaFormatFile(filename)
Chris Sosa5d342a22010-09-28 16:54:41 -0700813 if label:
Don Garrettf90edf02010-11-16 17:36:14 -0800814 url = '%s/%s/%s' % (static_urlbase, label, payload_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700815 else:
Don Garrettf90edf02010-11-16 17:36:14 -0800816 url = '%s/%s' % (static_urlbase, payload_path)
Chris Sosa5d342a22010-09-28 16:54:41 -0700817
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700818 _Log('Responding to client to use url %s to get image.' % url)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700819 return self.GetUpdatePayload(hash, sha256, size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700820 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700821 return self.GetNoUpdatePayload()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700822
823 def HandleHostInfoPing(self, ip):
824 """Returns host info dictionary for the given IP in JSON format."""
825 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800826 if ip in self.host_infos.table:
827 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
828
829 def HandleHostLogPing(self, ip):
830 """Returns a complete log of events for host in JSON format."""
831 if ip == 'all':
832 return json.dumps(
833 dict([(key, self.host_infos.table[key].log)
834 for key in self.host_infos.table]))
835 if ip in self.host_infos.table:
836 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700837
838 def HandleSetUpdatePing(self, ip, label):
839 """Sets forced_update_label for a given host."""
840 assert ip, 'No ip provided.'
841 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800842 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label