blob: cd265072a1e0fbf3e310b670869887e85a674a4a [file] [log] [blame]
Amin Hassani8d718d12019-06-03 04:28:391# -*- coding: utf-8 -*-
Darin Petkovc3fd90c2011-05-11 21:23:002# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
[email protected]ded22402009-10-26 22:36:213# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Gilad Arnoldd8d595c2014-03-21 20:00:416"""Devserver module for handling update client requests."""
7
Don Garrettfb15e322016-06-22 02:12:088from __future__ import print_function
9
[email protected]ded22402009-10-26 22:36:2110import os
Chris Sosa7c931362010-10-12 02:49:0111
Amin Hassani4f1e4622019-10-03 17:40:5012from six.moves import urllib
13
14import cherrypy # pylint: disable=import-error
Gilad Arnoldabb352e2012-09-23 08:24:2715
Amin Hassani8d718d12019-06-03 04:28:3916# TODO(crbug.com/872441): We try to import nebraska from different places
17# because when we install the devserver, we copy the nebraska.py into the main
18# directory. Once this bug is resolved, we can always import from nebraska
19# directory.
20try:
21 from nebraska import nebraska
22except ImportError:
23 import nebraska
Chris Sosa05491b12010-11-09 01:14:1624
Achuith Bhandarkar662fb722019-10-31 23:12:4925import setup_chromite # pylint: disable=unused-import
Achuith Bhandarkar662fb722019-10-31 23:12:4926from chromite.lib.xbuddy import cherrypy_log_util
Achuith Bhandarkar662fb722019-10-31 23:12:4927from chromite.lib.xbuddy import devserver_constants as constants
28
Gilad Arnoldc65330c2012-09-20 22:17:4829
30# Module-local log function.
Chris Sosa6a3697f2013-01-30 00:44:4331def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 23:12:4932 return cherrypy_log_util.LogWithTag('UPDATE', message, *args)
[email protected]ded22402009-10-26 22:36:2133
Gilad Arnold0c9c8602012-10-03 06:58:5834class AutoupdateError(Exception):
35 """Exception classes used by this module."""
36 pass
37
38
Don Garrett0ad09372010-12-07 00:20:3039def _ChangeUrlPort(url, new_port):
40 """Return the URL passed in with a different port"""
Amin Hassani4f1e4622019-10-03 17:40:5041 scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
Don Garrett0ad09372010-12-07 00:20:3042 host_port = netloc.split(':')
43
44 if len(host_port) == 1:
45 host_port.append(new_port)
46 else:
47 host_port[1] = new_port
48
Don Garrettfb15e322016-06-22 02:12:0849 print(host_port)
joychen121fc9b2013-08-02 21:30:3050 netloc = '%s:%s' % tuple(host_port)
Don Garrett0ad09372010-12-07 00:20:3051
Amin Hassani4f1e4622019-10-03 17:40:5052 # pylint: disable=too-many-function-args
53 return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
Don Garrett0ad09372010-12-07 00:20:3054
Chris Sosa6a3697f2013-01-30 00:44:4355def _NonePathJoin(*args):
56 """os.path.join that filters None's from the argument list."""
Amin Hassani4f1e4622019-10-03 17:40:5057 return os.path.join(*[x for x in args if x is not None])
Don Garrett0ad09372010-12-07 00:20:3058
Chris Sosa6a3697f2013-01-30 00:44:4359
Amin Hassani4b5d5ab2020-03-23 02:57:4660class Autoupdate(object):
Amin Hassanie9ffb862019-09-26 00:10:4061 """Class that contains functionality that handles Chrome OS update pings."""
[email protected]ded22402009-10-26 22:36:2162
Amin Hassanie5612032020-03-25 21:50:4263 def __init__(self, xbuddy, static_dir=None):
Amin Hassanie9ffb862019-09-26 00:10:4064 """Initializes the class.
65
66 Args:
67 xbuddy: The xbuddy path.
Amin Hassani4b5d5ab2020-03-23 02:57:4668 static_dir: The path to the devserver static directory.
Amin Hassanie9ffb862019-09-26 00:10:4069 """
joychen121fc9b2013-08-02 21:30:3070 self.xbuddy = xbuddy
Amin Hassani4b5d5ab2020-03-23 02:57:4671 self.static_dir = static_dir
Gilad Arnoldd0c71752013-12-06 19:48:4572
Amin Hassanie9ffb862019-09-26 00:10:4073 def GetUpdateForLabel(self, label):
joychen121fc9b2013-08-02 21:30:3074 """Given a label, get an update from the directory.
Chris Sosa0356d3b2010-09-16 22:46:2275
joychen121fc9b2013-08-02 21:30:3076 Args:
joychen121fc9b2013-08-02 21:30:3077 label: the relative directory inside the static dir
Gilad Arnoldd8d595c2014-03-21 20:00:4178
Chris Sosa6a3697f2013-01-30 00:44:4379 Returns:
joychen121fc9b2013-08-02 21:30:3080 A relative path to the directory with the update payload.
81 This is the label if an update did not need to be generated, but can
82 be label/cache/hashed_dir_for_update.
Gilad Arnoldd8d595c2014-03-21 20:00:4183
Chris Sosa6a3697f2013-01-30 00:44:4384 Raises:
joychen121fc9b2013-08-02 21:30:3085 AutoupdateError: If client version is higher than available update found
86 at the directory given by the label.
Don Garrettf90edf02010-11-17 01:36:1487 """
Amin Hassanie9ffb862019-09-26 00:10:4088 _Log('Update label: %s', label)
89 static_update_path = _NonePathJoin(self.static_dir, label,
90 constants.UPDATE_FILE)
Don Garrettee25e552010-11-23 20:09:3591
joychen121fc9b2013-08-02 21:30:3092 if label and os.path.exists(static_update_path):
93 # An update payload was found for the given label, return it.
94 return label
Don Garrett0c880e22010-11-18 02:13:3795
joychen121fc9b2013-08-02 21:30:3096 # The label didn't resolve.
Amin Hassanie9ffb862019-09-26 00:10:4097 _Log('Did not found any update payload for label %s.', label)
joychen121fc9b2013-08-02 21:30:3098 return None
Chris Sosa2c048f12010-10-27 23:05:2799
David Rileyee75de22017-11-02 17:48:15100 def GetDevserverUrl(self):
101 """Returns the devserver url base."""
Chris Sosa6a3697f2013-01-30 00:44:43102 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
103 if x_forwarded_host:
104 hostname = 'http://' + x_forwarded_host
105 else:
106 hostname = cherrypy.request.base
107
David Rileyee75de22017-11-02 17:48:15108 return hostname
109
110 def GetStaticUrl(self):
111 """Returns the static url base that should prefix all payload responses."""
112 hostname = self.GetDevserverUrl()
Amin Hassanie5612032020-03-25 21:50:42113 _Log('Handling update ping as %s', hostname)
David Rileyee75de22017-11-02 17:48:15114
Amin Hassanic9dd11e2019-07-11 22:33:55115 static_urlbase = '%s/static' % hostname
Chris Sosa6a3697f2013-01-30 00:44:43116 _Log('Using static url base %s', static_urlbase)
Chris Sosa6a3697f2013-01-30 00:44:43117 return static_urlbase
118
Amin Hassanie9ffb862019-09-26 00:10:40119 def GetPathToPayload(self, label, board):
joychen121fc9b2013-08-02 21:30:30120 """Find a payload locally.
121
122 See devserver's update rpc for documentation.
123
124 Args:
125 label: from update request
joychen121fc9b2013-08-02 21:30:30126 board: from update request
Gilad Arnoldd8d595c2014-03-21 20:00:41127
128 Returns:
joychen121fc9b2013-08-02 21:30:30129 The relative path to an update from the static_dir
Gilad Arnoldd8d595c2014-03-21 20:00:41130
joychen121fc9b2013-08-02 21:30:30131 Raises:
132 AutoupdateError: If the update could not be found.
133 """
Amin Hassaniabbb8842020-03-25 21:44:23134 label = label or ''
135 label_list = label.split('/')
136 # Suppose that the path follows old protocol of indexing straight
137 # into static_dir with board/version label.
138 # Attempt to get the update in that directory, generating if necc.
139 path_to_payload = self.GetUpdateForLabel(label)
140 if path_to_payload is None:
141 # There was no update found in the directory. Let XBuddy find the
142 # payloads.
143 if label_list[0] == 'xbuddy':
144 # If path explicitly calls xbuddy, pop off the tag.
145 label_list.pop()
146 x_label, _ = self.xbuddy.Translate(label_list, board=board)
147 # Path has been resolved, try to get the payload.
148 path_to_payload = self.GetUpdateForLabel(x_label)
joychen121fc9b2013-08-02 21:30:30149 if path_to_payload is None:
Amin Hassaniabbb8842020-03-25 21:44:23150 # No update payload found after translation. Try to get an update to
151 # a test image from GS using the label.
152 path_to_payload, _image_name = self.xbuddy.Get(
153 ['remote', label, 'full_payload'])
joychen121fc9b2013-08-02 21:30:30154
155 # One of the above options should have gotten us a relative path.
156 if path_to_payload is None:
157 raise AutoupdateError('Failed to get an update for: %s' % label)
Amin Hassani8d718d12019-06-03 04:28:39158
159 return path_to_payload
joychen121fc9b2013-08-02 21:30:30160
Amin Hassani6eec8792020-01-09 22:06:48161 def HandleUpdatePing(self, data, label='', **kwargs):
Chris Sosa6a3697f2013-01-30 00:44:43162 """Handles an update ping from an update client.
163
164 Args:
165 data: XML blob from client.
166 label: optional label for the update.
Amin Hassani6eec8792020-01-09 22:06:48167 kwargs: The map of query strings passed to the /update API.
Gilad Arnoldd8d595c2014-03-21 20:00:41168
Chris Sosa6a3697f2013-01-30 00:44:43169 Returns:
170 Update payload message for client.
171 """
172 # Get the static url base that will form that base of our update url e.g.
173 # http://hostname:8080/static/update.gz.
David Rileyee75de22017-11-02 17:48:15174 static_urlbase = self.GetStaticUrl()
Amin Hassani86c6fb52020-02-28 19:03:52175 # Change the URL's string query dictionary provided by cherrypy to a valid
176 # dictionary that has proper values for its keys. e.g. True instead of
177 # 'True'.
178 kwargs = nebraska.QueryDictToDict(kwargs)
Chris Sosa6a3697f2013-01-30 00:44:43179
Chris Sosab26b1202013-08-16 23:40:55180 # Process attributes of the update check.
Amin Hassani8d718d12019-06-03 04:28:39181 request = nebraska.Request(data)
Amin Hassani8d718d12019-06-03 04:28:39182 if request.request_type == nebraska.Request.RequestType.EVENT:
Gilad Arnolde7819e72014-03-21 19:50:48183 _Log('A non-update event notification received. Returning an ack.')
Amin Hassani083e3fe2020-02-13 19:39:18184 return nebraska.Nebraska().GetResponseToRequest(
185 request, response_props=nebraska.ResponseProperties(**kwargs))
Chris Sosa6a3697f2013-01-30 00:44:43186
Amin Hassani8d718d12019-06-03 04:28:39187 _Log('Update Check Received.')
Chris Sosa6a3697f2013-01-30 00:44:43188
189 try:
Amin Hassanie7ead902019-10-11 23:42:43190 path_to_payload = self.GetPathToPayload(label, request.board)
Amin Hassani8d718d12019-06-03 04:28:39191 base_url = _NonePathJoin(static_urlbase, path_to_payload)
Amin Hassanic9dd11e2019-07-11 22:33:55192 local_payload_dir = _NonePathJoin(self.static_dir, path_to_payload)
Chris Sosa6a3697f2013-01-30 00:44:43193 except AutoupdateError as e:
194 # Raised if we fail to generate an update payload.
Amin Hassani8d718d12019-06-03 04:28:39195 _Log('Failed to process an update request, but we will defer to '
196 'nebraska to respond with no-update. The error was %s', e)
Chris Sosa6a3697f2013-01-30 00:44:43197
Amin Hassani8d718d12019-06-03 04:28:39198 _Log('Responding to client to use url %s to get image', base_url)
Amin Hassanic91fc0d2019-12-04 19:07:16199 nebraska_props = nebraska.NebraskaProperties(
200 update_payloads_address=base_url,
201 update_metadata_dir=local_payload_dir)
Amin Hassanic91fc0d2019-12-04 19:07:16202 nebraska_obj = nebraska.Nebraska(nebraska_props=nebraska_props)
Amin Hassani083e3fe2020-02-13 19:39:18203 return nebraska_obj.GetResponseToRequest(
204 request, response_props=nebraska.ResponseProperties(**kwargs))