blob: e1b884a3baac29020fe738b463c26eaf453874a5 [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
Gilad Arnold0c9c8602012-10-02 23:58:58 -070012import urllib2
Don Garrett0ad09372010-12-06 16:20:30 -080013import urlparse
Chris Sosa7c931362010-10-11 19:49:01 -070014
Gilad Arnoldabb352e2012-09-23 01:24:27 -070015import cherrypy
16
17from build_util import BuildObject
Gilad Arnold55a2a372012-10-02 09:46:32 -070018import common_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070019import log_util
Chris Sosa05491b12010-11-08 17:14:16 -080020
Gilad Arnoldc65330c2012-09-20 15:17:48 -070021
22# Module-local log function.
23def _Log(message, *args, **kwargs):
24 return log_util.LogWithTag('UPDATE', message, *args, **kwargs)
25
rtc@google.comded22402009-10-26 22:36:21 +000026
Chris Sosa417e55d2011-01-25 16:40:48 -080027UPDATE_FILE = 'update.gz'
28STATEFUL_FILE = 'stateful.tgz'
29CACHE_DIR = 'cache'
Chris Sosa0356d3b2010-09-16 15:46:22 -070030
Don Garrett0ad09372010-12-06 16:20:30 -080031
Gilad Arnold0c9c8602012-10-02 23:58:58 -070032class AutoupdateError(Exception):
33 """Exception classes used by this module."""
34 pass
35
36
Don Garrett0ad09372010-12-06 16:20:30 -080037def _ChangeUrlPort(url, new_port):
38 """Return the URL passed in with a different port"""
39 scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
40 host_port = netloc.split(':')
41
42 if len(host_port) == 1:
43 host_port.append(new_port)
44 else:
45 host_port[1] = new_port
46
47 print host_port
48 netloc = "%s:%s" % tuple(host_port)
49
50 return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
51
52
Gilad Arnold286a0062012-01-12 13:47:02 -080053class HostInfo:
54 """Records information about an individual host.
55
56 Members:
57 attrs: Static attributes (legacy)
58 log: Complete log of recorded client entries
59 """
60
61 def __init__(self):
62 # A dictionary of current attributes pertaining to the host.
63 self.attrs = {}
64
65 # A list of pairs consisting of a timestamp and a dictionary of recorded
66 # attributes.
67 self.log = []
68
69 def __repr__(self):
70 return 'attrs=%s, log=%s' % (self.attrs, self.log)
71
72 def AddLogEntry(self, entry):
73 """Append a new log entry."""
74 # Append a timestamp.
75 assert not 'timestamp' in entry, 'Oops, timestamp field already in use'
76 entry['timestamp'] = time.strftime('%Y-%m-%d %H:%M:%S')
77 # Add entry to hosts' message log.
78 self.log.append(entry)
79
80 def SetAttr(self, attr, value):
81 """Set an attribute value."""
82 self.attrs[attr] = value
83
84 def GetAttr(self, attr):
85 """Returns the value of an attribute."""
86 if attr in self.attrs:
87 return self.attrs[attr]
88
89 def PopAttr(self, attr, default):
90 """Returns and deletes a particular attribute."""
91 return self.attrs.pop(attr, default)
92
93
94class HostInfoTable:
95 """Records information about a set of hosts who engage in update activity.
96
97 Members:
98 table: Table of information on hosts.
99 """
100
101 def __init__(self):
102 # A dictionary of host information. Keys are normally IP addresses.
103 self.table = {}
104
105 def __repr__(self):
106 return '%s' % self.table
107
108 def GetInitHostInfo(self, host_id):
109 """Return a host's info object, or create a new one if none exists."""
110 return self.table.setdefault(host_id, HostInfo())
111
112 def GetHostInfo(self, host_id):
113 """Return an info object for given host, if such exists."""
114 if host_id in self.table:
115 return self.table[host_id]
116
117
rtc@google.com64244662009-11-12 00:52:08 +0000118class Autoupdate(BuildObject):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700119 """Class that contains functionality that handles Chrome OS update pings.
120
121 Members:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700122 serve_only: serve only pre-built updates. static_dir must contain
123 update.gz and stateful.tgz.
124 factory_config: path to the factory config file if handling factory
125 requests.
126 use_test_image: use chromiumos_test_image.bin rather than the standard.
127 urlbase: base URL, other than devserver, for update images.
128 forced_image: path to an image to use for all updates.
129 payload_path: path to pre-generated payload to serve.
130 src_image: if specified, creates a delta payload from this image.
131 proxy_port: port of local proxy to tell client to connect to you
132 through.
133 vm: set for VM images (doesn't patch kernel)
134 board: board for the image. Needed for pre-generating of updates.
135 copy_to_static_root: copies images generated from the cache to ~/static.
136 private_key: path to private key in PEM format.
137 critical_update: whether provisioned payload is critical.
138 remote_payload: whether provisioned payload is remotely staged.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700139 """
rtc@google.comded22402009-10-26 22:36:21 +0000140
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700141 _PAYLOAD_URL_PREFIX = '/static/'
142 _FILEINFO_URL_PREFIX = '/api/fileinfo/'
143
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700144 def __init__(self, serve_only=None, test_image=False, urlbase=None,
Greg Spencerc8b59b22011-03-15 14:15:23 -0700145 factory_config_path=None,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700146 forced_image=None, payload_path=None,
147 proxy_port=None, src_image='', vm=False, board=None,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800148 copy_to_static_root=True, private_key=None,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700149 critical_update=False, remote_payload=False,
Chris Sosae67b78f2010-11-04 17:33:16 -0700150 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700151 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700152 self.serve_only = serve_only
Sean O'Connor1b4b0762010-06-02 17:37:32 -0700153 self.factory_config = factory_config_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700154 self.use_test_image = test_image
Chris Sosa5d342a22010-09-28 16:54:41 -0700155 if urlbase:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700156 self.urlbase = urlbase
Chris Sosa5d342a22010-09-28 16:54:41 -0700157 else:
Chris Sosa9841e1c2010-10-14 10:51:45 -0700158 self.urlbase = None
Chris Sosa5d342a22010-09-28 16:54:41 -0700159
Chris Sosa0356d3b2010-09-16 15:46:22 -0700160 self.forced_image = forced_image
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700161 self.payload_path = payload_path
Chris Sosa62f720b2010-10-26 21:39:48 -0700162 self.src_image = src_image
Don Garrett0ad09372010-12-06 16:20:30 -0800163 self.proxy_port = proxy_port
Chris Sosa4136e692010-10-28 23:42:37 -0700164 self.vm = vm
Chris Sosae67b78f2010-11-04 17:33:16 -0700165 self.board = board
Chris Sosa08d55a22011-01-19 16:08:02 -0800166 self.copy_to_static_root = copy_to_static_root
Chris Sosa0f1ec842011-02-14 16:33:22 -0800167 self.private_key = private_key
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800168 self.critical_update = critical_update
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700169 self.remote_payload = remote_payload
Don Garrettfff4c322010-11-19 13:37:12 -0800170
Chris Sosa417e55d2011-01-25 16:40:48 -0800171 # Path to pre-generated file.
172 self.pregenerated_path = None
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700173
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700174 # Initialize empty host info cache. Used to keep track of various bits of
Gilad Arnold286a0062012-01-12 13:47:02 -0800175 # information about a given host. A host is identified by its IP address.
176 # The info stored for each host includes a complete log of events for this
177 # host, as well as a dictionary of current attributes derived from events.
178 self.host_infos = HostInfoTable()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700179
Chris Sosa0356d3b2010-09-16 15:46:22 -0700180 def _GetSecondsSinceMidnight(self):
181 """Returns the seconds since midnight as a decimal value."""
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700182 now = time.localtime()
183 return now[3] * 3600 + now[4] * 60 + now[5]
184
Chris Sosa0356d3b2010-09-16 15:46:22 -0700185 def _GetDefaultBoardID(self):
186 """Returns the default board id stored in .default_board."""
187 board_file = '%s/.default_board' % (self.scripts_dir)
188 try:
189 return open(board_file).read()
190 except IOError:
191 return 'x86-generic'
192
193 def _GetLatestImageDir(self, board_id):
194 """Returns the latest image dir based on shell script."""
195 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
196 return os.popen(cmd).read().strip()
197
198 def _GetVersionFromDir(self, image_dir):
199 """Returns the version of the image based on the name of the directory."""
200 latest_version = os.path.basename(image_dir)
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700201 parts = latest_version.split('-')
202 if len(parts) == 2:
203 # Old-style, e.g. "0.15.938.2011_08_23_0941-a1".
204 # TODO(derat): Remove the code for old-style versions after 20120101.
205 return parts[0]
206 else:
207 # New-style, e.g. "R16-1102.0.2011_09_30_0806-a1".
208 return parts[1]
Chris Sosa0356d3b2010-09-16 15:46:22 -0700209
210 def _CanUpdate(self, client_version, latest_version):
Don Garrettf90edf02010-11-16 17:36:14 -0800211 """Returns true if the latest_version is greater than the client_version.
212 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700213 _Log('client version %s latest version %s'
214 % (client_version, latest_version))
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700215
216 client_tokens = client_version.replace('_', '').split('.')
217 # If the client has an old four-token version like "0.16.892.0", drop the
218 # first two tokens -- we use versions like "892.0.0" now.
219 # TODO(derat): Remove the code for old-style versions after 20120101.
220 if len(client_tokens) == 4:
221 client_tokens = client_tokens[2:]
222
223 latest_tokens = latest_version.replace('_', '').split('.')
224 if len(latest_tokens) == 4:
225 latest_tokens = latest_tokens[2:]
226
227 for i in range(min(len(client_tokens), len(latest_tokens))):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700228 if int(latest_tokens[i]) == int(client_tokens[i]):
229 continue
230 return int(latest_tokens[i]) > int(client_tokens[i])
Daniel Erat8a0bc4a2011-09-30 08:52:52 -0700231
232 # Favor four-token new-style versions on the server over old-style versions
233 # on the client if everything else matches.
234 return len(latest_tokens) > len(client_tokens)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700235
Chris Sosa0356d3b2010-09-16 15:46:22 -0700236 def _UnpackZip(self, image_dir):
237 """Unpacks an image.zip into a given directory."""
238 image = os.path.join(image_dir, self._GetImageName())
239 if os.path.exists(image):
240 return True
241 else:
242 # -n, never clobber an existing file, in case we get invoked
243 # simultaneously by multiple request handlers. This means that
244 # we're assuming each image.zip file lives in a versioned
245 # directory (a la Buildbot).
246 return os.system('cd %s && unzip -n image.zip' % image_dir) == 0
247
248 def _GetImageName(self):
249 """Returns the name of the image that should be used."""
250 if self.use_test_image:
251 image_name = 'chromiumos_test_image.bin'
252 else:
253 image_name = 'chromiumos_image.bin'
254 return image_name
255
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700256 def _IsDeltaFormatFile(self, filename):
257 try:
258 file_handle = open(filename, 'r')
259 delta_magic = 'CrAU'
260 magic = file_handle.read(len(delta_magic))
261 return magic == delta_magic
262 except Exception:
263 return False
264
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700265 def GetUpdatePayload(self, sha1, sha256, size, url, is_delta_format):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700266 """Returns a payload to the client corresponding to a new update.
267
268 Args:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700269 sha1: SHA1 hash of update blob
270 sha256: SHA256 hash of update blob
Chris Sosa0356d3b2010-09-16 15:46:22 -0700271 size: size of update blob
272 url: where to find update blob
273 Returns:
274 Xml string to be passed back to client.
275 """
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700276 delta = 'false'
277 if is_delta_format:
278 delta = 'true'
rtc@google.com21a5ca32009-11-04 18:23:23 +0000279 payload = """<?xml version="1.0" encoding="UTF-8"?>
280 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
Darin Petkov2b2ff4b2010-07-27 15:02:09 -0700281 <daystart elapsed_seconds="%s"/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000282 <app appid="{%s}" status="ok">
283 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700284 <updatecheck
Jay Srinivasan9a1c4572012-03-16 19:16:58 -0700285 ChromeOSVersion="9999.0.0"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700286 codebase="%s"
287 hash="%s"
Darin Petkov91436cb2010-09-28 08:52:17 -0700288 sha256="%s"
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700289 needsadmin="false"
290 size="%s"
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700291 IsDelta="%s"
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800292 status="ok"
293 %s/>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000294 </app>
295 </gupdate>
296 """
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800297 extra_attributes = []
298 if self.critical_update:
299 # The date string looks like '20111115' (2011-11-15). As of writing,
300 # there's no particular format for the deadline value that the
301 # client expects -- it's just empty vs. non-empty.
302 date_str = datetime.date.today().strftime('%Y%m%d')
303 extra_attributes.append('deadline="%s"' % date_str)
304 xml = payload % (self._GetSecondsSinceMidnight(),
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700305 self.app_id, url, sha1, sha256, size, delta,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800306 ' '.join(extra_attributes))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700307 _Log('Generated update payload: %s' % xml)
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800308 return xml
rtc@google.comded22402009-10-26 22:36:21 +0000309
rtc@google.com21a5ca32009-11-04 18:23:23 +0000310 def GetNoUpdatePayload(self):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700311 """Returns a payload to the client corresponding to no update."""
Darin Petkov845f1172011-01-05 14:45:24 -0800312 payload = """<?xml version="1.0" encoding="UTF-8"?>
313 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
314 <daystart elapsed_seconds="%s"/>
315 <app appid="{%s}" status="ok">
316 <ping status="ok"/>
317 <updatecheck status="noupdate"/>
318 </app>
319 </gupdate>
rtc@google.com21a5ca32009-11-04 18:23:23 +0000320 """
Chris Sosa0356d3b2010-09-16 15:46:22 -0700321 return payload % (self._GetSecondsSinceMidnight(), self.app_id)
rtc@google.comded22402009-10-26 22:36:21 +0000322
Don Garrettf90edf02010-11-16 17:36:14 -0800323 def GenerateUpdateFile(self, src_image, image_path, output_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700324 """Generates an update gz given a full path to an image.
325
326 Args:
327 image_path: Full path to image.
328 Returns:
329 Path to created update_payload or None on error.
330 """
Don Garrettfff4c322010-11-19 13:37:12 -0800331 update_path = os.path.join(output_dir, UPDATE_FILE)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700332 _Log('Generating update image %s' % update_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700333
Chris Sosa0f1ec842011-02-14 16:33:22 -0800334 update_command = [
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700335 'cros_generate_update_payload',
Chris Sosa0f1ec842011-02-14 16:33:22 -0800336 '--image="%s"' % image_path,
337 '--output="%s"' % update_path,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800338 ]
Chris Sosa4136e692010-10-28 23:42:37 -0700339
Chris Sosa0f1ec842011-02-14 16:33:22 -0800340 if src_image: update_command.append('--src_image="%s"' % src_image)
341 if not self.vm: update_command.append('--patch_kernel')
342 if self.private_key: update_command.append('--private_key="%s"' %
343 self.private_key)
344
345 update_string = ' '.join(update_command)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700346 _Log('Running ' + update_string)
Chris Sosa0f1ec842011-02-14 16:33:22 -0800347 if os.system(update_string) != 0:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700348 _Log('Failed to create update payload')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700349 return None
350
Don Garrettfff4c322010-11-19 13:37:12 -0800351 return UPDATE_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700352
Don Garrettf90edf02010-11-16 17:36:14 -0800353 def GenerateStatefulFile(self, image_path, output_dir):
354 """Generates a stateful update payload given a full path to an image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700355
356 Args:
357 image_path: Full path to image.
358 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800359 Path to created stateful update_payload or None on error.
Chris Sosa908fd6f2010-11-10 17:31:18 -0800360 Raises:
361 A subprocess exception if the update generator fails to generate a
362 stateful payload.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700363 """
Don Garrettfff4c322010-11-19 13:37:12 -0800364 output_gz = os.path.join(output_dir, STATEFUL_FILE)
Chris Sosa908fd6f2010-11-10 17:31:18 -0800365 subprocess.check_call(
Chris Sosa5b8b5eb2012-03-27 11:15:27 -0700366 ['cros_generate_stateful_update_payload',
Chris Sosa908fd6f2010-11-10 17:31:18 -0800367 '--image=%s' % image_path,
Don Garrettf90edf02010-11-16 17:36:14 -0800368 '--output_dir=%s' % output_dir,
Chris Sosa908fd6f2010-11-10 17:31:18 -0800369 ])
Don Garrettfff4c322010-11-19 13:37:12 -0800370 return STATEFUL_FILE
Chris Sosa0356d3b2010-09-16 15:46:22 -0700371
Don Garrettf90edf02010-11-16 17:36:14 -0800372 def FindCachedUpdateImageSubDir(self, src_image, dest_image):
373 """Find directory to store a cached update.
374
Gilad Arnold55a2a372012-10-02 09:46:32 -0700375 Given one, or two images for an update, this finds which cache directory
376 should hold the update files, even if they don't exist yet.
Don Garrettf90edf02010-11-16 17:36:14 -0800377
Gilad Arnold55a2a372012-10-02 09:46:32 -0700378 Returns:
379 A directory path for storing a cached update, of the following form:
380 Non-delta updates:
381 CACHE_DIR/<dest_hash>
382 Delta updates:
383 CACHE_DIR/<src_hash>_<dest_hash>
384 Signed updates (self.private_key):
385 CACHE_DIR/<src_hash>_<dest_hash>+<private_key_hash>
Chris Sosa744e1472011-09-07 19:32:50 -0700386 """
Gilad Arnold55a2a372012-10-02 09:46:32 -0700387 update_dir = ''
Chris Sosa744e1472011-09-07 19:32:50 -0700388 if src_image:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700389 update_dir += common_util.GetFileMd5(src_image) + '_'
Don Garrettf90edf02010-11-16 17:36:14 -0800390
Gilad Arnold55a2a372012-10-02 09:46:32 -0700391 update_dir += common_util.GetFileMd5(dest_image)
Chris Sosa744e1472011-09-07 19:32:50 -0700392 if self.private_key:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700393 update_dir += '+' + common_util.GetFileMd5(self.private_key)
Chris Sosa744e1472011-09-07 19:32:50 -0700394
Chris Sosa9fba7562012-01-31 10:15:47 -0800395 if not self.vm:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700396 update_dir += '+patched_kernel'
Chris Sosa9fba7562012-01-31 10:15:47 -0800397
Gilad Arnold55a2a372012-10-02 09:46:32 -0700398 return os.path.join(CACHE_DIR, update_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800399
Don Garrettfff4c322010-11-19 13:37:12 -0800400 def GenerateUpdateImage(self, image_path, output_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800401 """Force generates an update payload based on the given image_path.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700402
Chris Sosade91f672010-11-16 10:05:44 -0800403 Args:
Don Garrettf90edf02010-11-16 17:36:14 -0800404 src_image: image we are updating from (Null/empty for non-delta)
405 image_path: full path to the image.
406 output_dir: the directory to write the update payloads in
Chris Sosade91f672010-11-16 10:05:44 -0800407 Returns:
Don Garrettfff4c322010-11-19 13:37:12 -0800408 update payload name relative to output_dir
Chris Sosade91f672010-11-16 10:05:44 -0800409 """
Don Garrettf90edf02010-11-16 17:36:14 -0800410 update_file = None
411 stateful_update_file = None
Andrew de los Reyes9a528712010-06-30 10:29:43 -0700412
Don Garrettf90edf02010-11-16 17:36:14 -0800413 # Actually do the generation
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700414 _Log('Generating update for image %s' % image_path)
Don Garrettfff4c322010-11-19 13:37:12 -0800415 update_file = self.GenerateUpdateFile(self.src_image,
Don Garrettf90edf02010-11-16 17:36:14 -0800416 image_path,
417 output_dir)
rtc@google.comded22402009-10-26 22:36:21 +0000418
Don Garrettf90edf02010-11-16 17:36:14 -0800419 if update_file:
420 stateful_update_file = self.GenerateStatefulFile(image_path,
421 output_dir)
422
423 if update_file and stateful_update_file:
Don Garrettfff4c322010-11-19 13:37:12 -0800424 return update_file
Chris Sosa417e55d2011-01-25 16:40:48 -0800425 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700426 _Log('Failed to generate update.')
Chris Sosa417e55d2011-01-25 16:40:48 -0800427 return None
Don Garrettf90edf02010-11-16 17:36:14 -0800428
429 def GenerateUpdateImageWithCache(self, image_path, static_image_dir):
430 """Force generates an update payload based on the given image_path.
rtc@google.comded22402009-10-26 22:36:21 +0000431
Chris Sosa0356d3b2010-09-16 15:46:22 -0700432 Args:
433 image_path: full path to the image.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700434 static_image_dir: the directory to move images to after generating.
435 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800436 update filename (not directory) relative to static_image_dir on success,
Chris Sosa417e55d2011-01-25 16:40:48 -0800437 or None.
Chris Sosa0356d3b2010-09-16 15:46:22 -0700438 """
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700439 _Log('Generating update for src %s image %s' % (self.src_image, image_path))
Chris Sosae67b78f2010-11-04 17:33:16 -0700440
Chris Sosa417e55d2011-01-25 16:40:48 -0800441 # If it was pregenerated_path, don't regenerate
442 if self.pregenerated_path:
443 return self.pregenerated_path
Don Garrettfff4c322010-11-19 13:37:12 -0800444
Don Garrettf90edf02010-11-16 17:36:14 -0800445 # Which sub_dir of static_image_dir should hold our cached update image
446 cache_sub_dir = self.FindCachedUpdateImageSubDir(self.src_image, image_path)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700447 _Log('Caching in sub_dir "%s"' % cache_sub_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800448
Chris Sosa417e55d2011-01-25 16:40:48 -0800449 update_path = os.path.join(cache_sub_dir, UPDATE_FILE)
450
Don Garrettf90edf02010-11-16 17:36:14 -0800451 # The cached payloads exist in a cache dir
452 cache_update_payload = os.path.join(static_image_dir,
Chris Sosa417e55d2011-01-25 16:40:48 -0800453 update_path)
Don Garrettf90edf02010-11-16 17:36:14 -0800454 cache_stateful_payload = os.path.join(static_image_dir,
455 cache_sub_dir,
Don Garrettfff4c322010-11-19 13:37:12 -0800456 STATEFUL_FILE)
Don Garrettf90edf02010-11-16 17:36:14 -0800457
Chris Sosa417e55d2011-01-25 16:40:48 -0800458 # Check to see if this cache directory is valid.
459 if not os.path.exists(cache_update_payload) or not os.path.exists(
460 cache_stateful_payload):
Don Garrettf90edf02010-11-16 17:36:14 -0800461 full_cache_dir = os.path.join(static_image_dir, cache_sub_dir)
Chris Sosa417e55d2011-01-25 16:40:48 -0800462 # Clean up stale state.
463 os.system('rm -rf "%s"' % full_cache_dir)
464 os.makedirs(full_cache_dir)
465 return_path = self.GenerateUpdateImage(image_path,
466 full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800467
Chris Sosa417e55d2011-01-25 16:40:48 -0800468 # Clean up cache dir since it's not valid.
469 if not return_path:
470 os.system('rm -rf "%s"' % full_cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800471 return None
Chris Sosa417e55d2011-01-25 16:40:48 -0800472
473 self.pregenerated_path = update_path
Don Garrettf90edf02010-11-16 17:36:14 -0800474
Chris Sosa08d55a22011-01-19 16:08:02 -0800475 # Generation complete, copy if requested.
476 if self.copy_to_static_root:
Chris Sosa417e55d2011-01-25 16:40:48 -0800477 # The final results exist directly in static
478 update_payload = os.path.join(static_image_dir,
479 UPDATE_FILE)
480 stateful_payload = os.path.join(static_image_dir,
481 STATEFUL_FILE)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700482 common_util.CopyFile(cache_update_payload, update_payload)
483 common_util.CopyFile(cache_stateful_payload, stateful_payload)
Chris Sosa417e55d2011-01-25 16:40:48 -0800484 return UPDATE_FILE
485 else:
486 return self.pregenerated_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700487
488 def GenerateLatestUpdateImage(self, board_id, client_version,
Don Garrettf90edf02010-11-16 17:36:14 -0800489 static_image_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700490 """Generates an update using the latest image that has been built.
491
492 This will only generate an update if the newest update is newer than that
493 on the client or client_version is 'ForcedUpdate'.
494
495 Args:
496 board_id: Name of the board.
497 client_version: Current version of the client or 'ForcedUpdate'
498 static_image_dir: the directory to move images to after generating.
499 Returns:
Don Garrettf90edf02010-11-16 17:36:14 -0800500 Name of the update image relative to static_image_dir or None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700501 """
502 latest_image_dir = self._GetLatestImageDir(board_id)
503 latest_version = self._GetVersionFromDir(latest_image_dir)
504 latest_image_path = os.path.join(latest_image_dir, self._GetImageName())
505
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700506 _Log('Preparing to generate update from latest built image %s.' %
507 latest_image_path)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700508
509 # Check to see whether or not we should update.
510 if client_version != 'ForcedUpdate' and not self._CanUpdate(
511 client_version, latest_version):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700512 _Log('no update')
Don Garrettf90edf02010-11-16 17:36:14 -0800513 return None
Chris Sosa0356d3b2010-09-16 15:46:22 -0700514
Don Garrettf90edf02010-11-16 17:36:14 -0800515 return self.GenerateUpdateImageWithCache(latest_image_path,
516 static_image_dir=static_image_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700517
Andrew de los Reyes52620802010-04-12 13:40:07 -0700518 def ImportFactoryConfigFile(self, filename, validate_checksums=False):
519 """Imports a factory-floor server configuration file. The file should
520 be in this format:
521 config = [
522 {
523 'qual_ids': set([1, 2, 3, "x86-generic"]),
524 'factory_image': 'generic-factory.gz',
525 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
526 'release_image': 'generic-release.gz',
527 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
528 'oempartitionimg_image': 'generic-oem.gz',
529 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700530 'efipartitionimg_image': 'generic-efi.gz',
531 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700532 'stateimg_image': 'generic-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800533 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800534 'firmware_image': 'generic-firmware.gz',
535 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700536 },
537 {
538 'qual_ids': set([6]),
539 'factory_image': '6-factory.gz',
540 'factory_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
541 'release_image': '6-release.gz',
542 'release_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
543 'oempartitionimg_image': '6-oem.gz',
544 'oempartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Nick Sanderse1eea922010-05-19 22:17:08 -0700545 'efipartitionimg_image': '6-efi.gz',
546 'efipartitionimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700547 'stateimg_image': '6-state.gz',
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800548 'stateimg_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Tom Wai-Hong Tamdac3df12010-06-14 09:56:15 +0800549 'firmware_image': '6-firmware.gz',
550 'firmware_checksum': 'AtiI8B64agHVN+yeBAyiNMX3+HM=',
Andrew de los Reyes52620802010-04-12 13:40:07 -0700551 },
552 ]
553 The server will look for the files by name in the static files
554 directory.
Chris Sosaa73ec162010-05-03 20:18:02 -0700555
Andrew de los Reyes52620802010-04-12 13:40:07 -0700556 If validate_checksums is True, validates checksums and exits. If
557 a checksum mismatch is found, it's printed to the screen.
558 """
559 f = open(filename, 'r')
560 output = {}
561 exec(f.read(), output)
562 self.factory_config = output['config']
563 success = True
564 for stanza in self.factory_config:
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800565 for key in stanza.copy().iterkeys():
566 suffix = '_image'
567 if key.endswith(suffix):
568 kind = key[:-len(suffix)]
Gilad Arnold55a2a372012-10-02 09:46:32 -0700569 stanza[kind + '_size'] = common_util.GetFileSize(os.path.join(
Chris Sosa0356d3b2010-09-16 15:46:22 -0700570 self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800571 if validate_checksums:
Gilad Arnold55a2a372012-10-02 09:46:32 -0700572 factory_checksum = common_util.GetFileSha1(
573 os.path.join(self.static_dir, stanza[kind + '_image']))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800574 if factory_checksum != stanza[kind + '_checksum']:
Chris Sosa0356d3b2010-09-16 15:46:22 -0700575 print ('Error: checksum mismatch for %s. Expected "%s" but file '
576 'has checksum "%s".' % (stanza[kind + '_image'],
577 stanza[kind + '_checksum'],
578 factory_checksum))
Tom Wai-Hong Tam65fc6072010-05-20 11:44:26 +0800579 success = False
Chris Sosa0356d3b2010-09-16 15:46:22 -0700580
Andrew de los Reyes52620802010-04-12 13:40:07 -0700581 if validate_checksums:
582 if success is False:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700583 raise AutoupdateError('Checksum mismatch in conf file.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700584
Andrew de los Reyes52620802010-04-12 13:40:07 -0700585 print 'Config file looks good.'
586
587 def GetFactoryImage(self, board_id, channel):
Nick Sanders723f3262010-09-16 05:18:41 -0700588 kind = channel.rsplit('-', 1)[0]
Andrew de los Reyes52620802010-04-12 13:40:07 -0700589 for stanza in self.factory_config:
590 if board_id not in stanza['qual_ids']:
591 continue
Nick Sanders15cd6ae2010-06-30 12:30:56 -0700592 if kind + '_image' not in stanza:
593 break
Andrew de los Reyes52620802010-04-12 13:40:07 -0700594 return (stanza[kind + '_image'],
595 stanza[kind + '_checksum'],
596 stanza[kind + '_size'])
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700597 return None, None, None
rtc@google.comded22402009-10-26 22:36:21 +0000598
Chris Sosa7c931362010-10-11 19:49:01 -0700599 def HandleFactoryRequest(self, board_id, channel):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700600 (filename, checksum, size) = self.GetFactoryImage(board_id, channel)
601 if filename is None:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700602 _Log('unable to find image for board %s' % board_id)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700603 return self.GetNoUpdatePayload()
Chris Sosa05f95162010-10-14 18:01:52 -0700604 url = '%s/static/%s' % (self.hostname, filename)
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700605 is_delta_format = self._IsDeltaFormatFile(filename)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700606 _Log('returning update payload ' + url)
Darin Petkov91436cb2010-09-28 08:52:17 -0700607 # Factory install is using memento updater which is using the sha-1 hash so
608 # setting sha-256 to an empty string.
Andrew de los Reyes5679b972010-10-25 17:34:49 -0700609 return self.GetUpdatePayload(checksum, '', size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700610
Chris Sosa151643e2010-10-28 14:40:57 -0700611 def GenerateUpdatePayloadForNonFactory(self, board_id, client_version,
612 static_image_dir):
Don Garrettf90edf02010-11-16 17:36:14 -0800613 """Generates an update for non-factory image.
Don Garrett710470d2010-11-15 17:43:44 -0800614
Don Garrettf90edf02010-11-16 17:36:14 -0800615 Returns:
616 file name relative to static_image_dir on success.
617 """
Dale Curtis723ec472010-11-30 14:06:47 -0800618 dest_path = os.path.join(static_image_dir, UPDATE_FILE)
619 dest_stateful = os.path.join(static_image_dir, STATEFUL_FILE)
620
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700621 if self.payload_path:
Don Garrett0c880e22010-11-17 18:13:37 -0800622 # If the forced payload is not already in our static_image_dir,
623 # copy it there.
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700624 src_path = os.path.abspath(self.payload_path)
Don Garrettee25e552010-11-23 12:09:35 -0800625 src_stateful = os.path.join(os.path.dirname(src_path),
626 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800627
628 # Only copy the files if the source directory is different from dest.
629 if os.path.dirname(src_path) != os.path.abspath(static_image_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700630 common_util.CopyFile(src_path, dest_path)
Don Garrettee25e552010-11-23 12:09:35 -0800631
632 # The stateful payload is optional.
633 if os.path.exists(src_stateful):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700634 common_util.CopyFile(src_stateful, dest_stateful)
Don Garrettee25e552010-11-23 12:09:35 -0800635 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700636 _Log('WARN: %s not found. Expected for dev and test builds.' %
637 STATEFUL_FILE)
Don Garrettee25e552010-11-23 12:09:35 -0800638 if os.path.exists(dest_stateful):
639 os.remove(dest_stateful)
Don Garrett0c880e22010-11-17 18:13:37 -0800640
Don Garrettfff4c322010-11-19 13:37:12 -0800641 return UPDATE_FILE
Don Garrett0c880e22010-11-17 18:13:37 -0800642 elif self.forced_image:
Don Garrettf90edf02010-11-16 17:36:14 -0800643 return self.GenerateUpdateImageWithCache(
644 self.forced_image,
645 static_image_dir=static_image_dir)
646 elif self.serve_only:
Dale Curtis723ec472010-11-30 14:06:47 -0800647 # Warn if update or stateful files can't be found.
648 if not os.path.exists(dest_path):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700649 _Log('WARN: %s not found. Expected for dev and test builds.' %
650 UPDATE_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800651
652 if not os.path.exists(dest_stateful):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700653 _Log('WARN: %s not found. Expected for dev and test builds.' %
654 STATEFUL_FILE)
Dale Curtis723ec472010-11-30 14:06:47 -0800655
656 return UPDATE_FILE
Don Garrettf90edf02010-11-16 17:36:14 -0800657 else:
658 if board_id:
659 return self.GenerateLatestUpdateImage(board_id,
660 client_version,
661 static_image_dir)
662
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700663 _Log('Failed to genereate update. '
664 'You must set --board when pre-generating latest update.')
Don Garrettf90edf02010-11-16 17:36:14 -0800665 return None
Chris Sosa2c048f12010-10-27 16:05:27 -0700666
667 def PreGenerateUpdate(self):
Chris Sosa417e55d2011-01-25 16:40:48 -0800668 """Pre-generates an update and prints out the relative path it.
669
670 Returns relative path of the update on success.
Don Garrettf90edf02010-11-16 17:36:14 -0800671 """
Chris Sosa2c048f12010-10-27 16:05:27 -0700672 # Does not work with factory config.
673 assert(not self.factory_config)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700674 _Log('Pre-generating the update payload.')
Chris Sosa2c048f12010-10-27 16:05:27 -0700675 # Does not work with labels so just use static dir.
Chris Sosa417e55d2011-01-25 16:40:48 -0800676 pregenerated_update = self.GenerateUpdatePayloadForNonFactory(
677 self.board, '0.0.0.0', self.static_dir)
678 if pregenerated_update:
679 print 'PREGENERATED_UPDATE=%s' % pregenerated_update
680
681 return pregenerated_update
Chris Sosa2c048f12010-10-27 16:05:27 -0700682
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700683 def _GetRemotePayloadAttrs(self, url):
684 """Returns hashes, size and delta flag of a remote update payload.
685
686 Obtain attributes of a payload file available on a remote devserver. This
687 is based on the assumption that the payload URL uses the /static prefix. We
688 need to make sure that both clients (requests) and remote devserver
689 (provisioning) preserve this invariant.
690
691 Args:
692 url: URL of statically staged remote file (http://host:port/static/...)
693 Returns:
694 A tuple containing the SHA1, SHA256, file size and whether or not it's a
695 delta payload (Boolean).
696 """
697 if self._PAYLOAD_URL_PREFIX not in url:
698 raise AutoupdateError(
699 'Payload URL does not have the expected prefix (%s)' %
700 self._PAYLOAD_URL_PREFIX)
701 fileinfo_url = url.replace(self._PAYLOAD_URL_PREFIX,
702 self._FILEINFO_URL_PREFIX)
703 _Log('retrieving file info for remote payload via %s' % fileinfo_url)
704 try:
705 conn = urllib2.urlopen(fileinfo_url)
706 file_attr_dict = json.loads(conn.read())
707 sha1 = file_attr_dict['sha1']
708 sha256 = file_attr_dict['sha256']
709 size = file_attr_dict['size']
710 except Exception, e:
711 _Log('failed to obtain remote payload info: %s' % str(e))
712 raise
713 is_delta_format = ('_mton' in url) or ('_nton' in url)
714
715 return sha1, sha256, size, is_delta_format
716
717 def _GetLocalPayloadAttrs(self, static_image_dir, payload_path):
718 """Returns hashes, size and delta flag of a local update payload.
719
720 Args:
721 static_image_dir: directory where static files are being staged
722 payload_path: path to the payload file inside the static directory
723 Returns:
724 A tuple containing the SHA1, SHA256, file size and whether or not it's a
725 delta payload (Boolean).
726 """
727 filename = os.path.join(static_image_dir, payload_path)
728 sha1 = common_util.GetFileSha1(filename)
729 sha256 = common_util.GetFileSha256(filename)
730 size = common_util.GetFileSize(filename)
731 is_delta_format = self._IsDeltaFormatFile(filename)
732 return sha1, sha256, size, is_delta_format
733
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700734 def HandleUpdatePing(self, data, label=None):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700735 """Handles an update ping from an update client.
736
737 Args:
738 data: xml blob from client.
739 label: optional label for the update.
740 Returns:
741 Update payload message for client.
742 """
Chris Sosa9841e1c2010-10-14 10:51:45 -0700743 # Set hostname as the hostname that the client is calling to and set up
Chris Sosa28be7db2012-06-13 16:26:10 -0700744 # the url base. If behind apache mod_proxy | mod_rewrite, the hostname will
745 # be in X-Forwarded-Host.
746 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
747 if x_forwarded_host:
748 self.hostname = 'http://' + x_forwarded_host
749 else:
750 self.hostname = cherrypy.request.base
751
Chris Sosa9841e1c2010-10-14 10:51:45 -0700752 if self.urlbase:
753 static_urlbase = self.urlbase
754 elif self.serve_only:
755 static_urlbase = '%s/static/archive' % self.hostname
756 else:
757 static_urlbase = '%s/static' % self.hostname
758
Don Garrett0ad09372010-12-06 16:20:30 -0800759 # If we have a proxy port, adjust the URL we instruct the client to
760 # use to go through the proxy.
761 if self.proxy_port:
762 static_urlbase = _ChangeUrlPort(static_urlbase, self.proxy_port)
763
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700764 _Log('Using static url base %s' % static_urlbase)
765 _Log('Handling update ping as %s: %s' % (self.hostname, data))
Chris Sosa0356d3b2010-09-16 15:46:22 -0700766
Chris Sosa9841e1c2010-10-14 10:51:45 -0700767 update_dom = minidom.parseString(data)
768 root = update_dom.firstChild
Chris Sosa0356d3b2010-09-16 15:46:22 -0700769
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700770 # Determine request IP, strip any IPv6 data for simplicity.
771 client_ip = cherrypy.request.remote.ip.split(':')[-1]
772
Gilad Arnold286a0062012-01-12 13:47:02 -0800773 # Obtain (or init) info object for this client.
774 curr_host_info = self.host_infos.GetInitHostInfo(client_ip)
775
776 # Initialize an empty dictionary for event attributes.
777 log_message = {}
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700778
779 # Store event details in the host info dictionary for API usage.
780 event = root.getElementsByTagName('o:event')
781 if event:
Gilad Arnold286a0062012-01-12 13:47:02 -0800782 event_result = int(event[0].getAttribute('eventresult'))
783 event_type = int(event[0].getAttribute('eventtype'))
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700784 client_previous_version = (event[0].getAttribute('previousversion')
785 if event[0].hasAttribute('previousversion')
786 else None)
Gilad Arnold286a0062012-01-12 13:47:02 -0800787 # Store attributes to legacy host info structure
788 curr_host_info.attrs['last_event_status'] = event_result
789 curr_host_info.attrs['last_event_type'] = event_type
790 # Add attributes to log message
791 log_message['event_result'] = event_result
792 log_message['event_type'] = event_type
Gilad Arnoldb11a8942012-03-13 15:33:21 -0700793 if client_previous_version is not None:
794 log_message['previous_version'] = client_previous_version
Gilad Arnold286a0062012-01-12 13:47:02 -0800795
796 # Get information about the requester.
797 query = root.getElementsByTagName('o:app')[0]
798 if query:
799 client_version = query.getAttribute('version')
800 channel = query.getAttribute('track')
801 board_id = (query.hasAttribute('board') and query.getAttribute('board')
802 or self._GetDefaultBoardID())
803 # Add attributes to log message
804 log_message['version'] = client_version
805 log_message['track'] = channel
806 log_message['board'] = board_id
807
808 # Log client's message
809 curr_host_info.AddLogEntry(log_message)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700810
Chris Sosa0356d3b2010-09-16 15:46:22 -0700811 # We only generate update payloads for updatecheck requests.
812 update_check = root.getElementsByTagName('o:updatecheck')
813 if not update_check:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700814 _Log('Non-update check received. Returning blank payload.')
Chris Sosa0356d3b2010-09-16 15:46:22 -0700815 # TODO(sosa): Generate correct non-updatecheck payload to better test
816 # update clients.
817 return self.GetNoUpdatePayload()
818
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700819 # Store version for this host in the cache.
Gilad Arnold286a0062012-01-12 13:47:02 -0800820 curr_host_info.attrs['last_known_version'] = client_version
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700821
822 # Check if an update has been forced for this client.
Gilad Arnold286a0062012-01-12 13:47:02 -0800823 forced_update = curr_host_info.PopAttr('forced_update_label', None)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700824 if forced_update:
825 label = forced_update
826
Chris Sosa0356d3b2010-09-16 15:46:22 -0700827 # Separate logic as Factory requests have static url's that override
828 # other options.
Andrew de los Reyes52620802010-04-12 13:40:07 -0700829 if self.factory_config:
Chris Sosa7c931362010-10-11 19:49:01 -0700830 return self.HandleFactoryRequest(board_id, channel)
Nick Sanders723f3262010-09-16 05:18:41 -0700831 else:
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700832 url = ''
833 # Are we provisioning a remote or local payload?
834 if self.remote_payload:
835 # If no explicit label was provided, use the value of --payload.
836 if not label and self.payload_path:
837 label = self.payload_path
Chris Sosa0356d3b2010-09-16 15:46:22 -0700838
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700839 # Form the URL of the update payload. This assumes that the payload
840 # file name is a devserver constant (which currently is the case).
841 url = '/'.join(filter(None, [static_urlbase, label, UPDATE_FILE]))
Chris Sosa5d342a22010-09-28 16:54:41 -0700842
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700843 # Get remote payload attributes.
844 sha1, sha256, file_size, is_delta_format = \
845 self._GetRemotePayloadAttrs(url)
846 else:
847 # Generate payload.
848 static_image_dir = os.path.join(*filter(None, [self.static_dir, label]))
849 payload_path = self.GenerateUpdatePayloadForNonFactory(
850 board_id, client_version, static_image_dir)
851 # If properly generated, obtain the payload URL and attributes.
852 if payload_path:
853 url = '/'.join(filter(None, [static_urlbase, label, payload_path]))
854 sha1, sha256, file_size, is_delta_format = \
855 self._GetLocalPayloadAttrs(static_image_dir, payload_path)
856
857 # If we end up with an actual payload path, generate a response.
858 if url:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700859 _Log('Responding to client to use url %s to get image.' % url)
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700860 return self.GetUpdatePayload(
861 sha1, sha256, file_size, url, is_delta_format)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700862 else:
Nick Sanders723f3262010-09-16 05:18:41 -0700863 return self.GetNoUpdatePayload()
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700864
865 def HandleHostInfoPing(self, ip):
866 """Returns host info dictionary for the given IP in JSON format."""
867 assert ip, 'No ip provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800868 if ip in self.host_infos.table:
869 return json.dumps(self.host_infos.GetHostInfo(ip).attrs)
870
871 def HandleHostLogPing(self, ip):
872 """Returns a complete log of events for host in JSON format."""
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700873 # If all events requested, return a dictionary of logs keyed by IP address.
Gilad Arnold286a0062012-01-12 13:47:02 -0800874 if ip == 'all':
875 return json.dumps(
876 dict([(key, self.host_infos.table[key].log)
877 for key in self.host_infos.table]))
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700878
879 # Otherwise we're looking for a specific IP address, so find its log.
Gilad Arnold286a0062012-01-12 13:47:02 -0800880 if ip in self.host_infos.table:
881 return json.dumps(self.host_infos.GetHostInfo(ip).log)
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700882
Gilad Arnold4ba437d2012-10-05 15:28:27 -0700883 # If no events were logged for this IP, return an empty log.
884 return json.dumps([])
885
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700886 def HandleSetUpdatePing(self, ip, label):
887 """Sets forced_update_label for a given host."""
888 assert ip, 'No ip provided.'
889 assert label, 'No label provided.'
Gilad Arnold286a0062012-01-12 13:47:02 -0800890 self.host_infos.GetInitHostInfo(ip).attrs['forced_update_label'] = label