blob: b567e11916a92a0b27cc8c5fe16d8e3053788b8b [file] [log] [blame]
Amin Hassani2aa34282020-11-18 01:18:19 +00001# -*- coding: utf-8 -*-
2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Devserver module for handling update client requests."""
7
8from __future__ import print_function
9
10import os
11
12from six.moves import urllib
13
14import cherrypy # pylint: disable=import-error
15
16# 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
24
25import setup_chromite # pylint: disable=unused-import
26from chromite.lib.xbuddy import cherrypy_log_util
Amin Hassani2aa34282020-11-18 01:18:19 +000027
28
29# Module-local log function.
30def _Log(message, *args):
31 return cherrypy_log_util.LogWithTag('UPDATE', message, *args)
32
33class AutoupdateError(Exception):
34 """Exception classes used by this module."""
35 pass
36
37
38def _ChangeUrlPort(url, new_port):
39 """Return the URL passed in with a different port"""
40 scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
41 host_port = netloc.split(':')
42
43 if len(host_port) == 1:
44 host_port.append(new_port)
45 else:
46 host_port[1] = new_port
47
48 print(host_port)
49 netloc = '%s:%s' % tuple(host_port)
50
51 # pylint: disable=too-many-function-args
52 return urllib.parse.urlunsplit((scheme, netloc, path, query, fragment))
53
54def _NonePathJoin(*args):
55 """os.path.join that filters None's from the argument list."""
56 return os.path.join(*[x for x in args if x is not None])
57
58
59class Autoupdate(object):
60 """Class that contains functionality that handles Chrome OS update pings."""
61
62 def __init__(self, xbuddy, static_dir=None):
63 """Initializes the class.
64
65 Args:
66 xbuddy: The xbuddy path.
67 static_dir: The path to the devserver static directory.
68 """
69 self.xbuddy = xbuddy
70 self.static_dir = static_dir
71
Amin Hassani2aa34282020-11-18 01:18:19 +000072 def GetDevserverUrl(self):
73 """Returns the devserver url base."""
74 x_forwarded_host = cherrypy.request.headers.get('X-Forwarded-Host')
75 if x_forwarded_host:
76 # Select the left most <ip>:<port> value so that the request is
77 # forwarded correctly.
78 x_forwarded_host = [x.strip() for x in x_forwarded_host.split(',')][0]
79 hostname = 'http://' + x_forwarded_host
80 else:
81 hostname = cherrypy.request.base
82
83 return hostname
84
85 def GetStaticUrl(self):
86 """Returns the static url base that should prefix all payload responses."""
87 hostname = self.GetDevserverUrl()
88 _Log('Handling update ping as %s', hostname)
89
90 static_urlbase = '%s/static' % hostname
91 _Log('Using static url base %s', static_urlbase)
92 return static_urlbase
93
94 def GetPathToPayload(self, label, board):
95 """Find a payload locally.
96
97 See devserver's update rpc for documentation.
98
99 Args:
100 label: from update request
101 board: from update request
102
103 Returns:
104 The relative path to an update from the static_dir
105
106 Raises:
107 AutoupdateError: If the update could not be found.
108 """
109 label = label or ''
110 label_list = label.split('/')
Amin Hassani8b503582020-12-02 20:47:01 -0800111 # There was no update found in the directory. Let XBuddy find the
112 # payloads.
113 if label_list[0] == 'xbuddy':
114 # If path explicitly calls xbuddy, pop off the tag.
115 label_list.pop()
116 x_label, _ = self.xbuddy.Translate(label_list, board=board)
117 # Path has been resolved, try to get the payload.
118 return _NonePathJoin(self.static_dir, x_label)
Amin Hassani2aa34282020-11-18 01:18:19 +0000119
120 def HandleUpdatePing(self, data, label='', **kwargs):
121 """Handles an update ping from an update client.
122
123 Args:
124 data: XML blob from client.
125 label: optional label for the update.
126 kwargs: The map of query strings passed to the /update API.
127
128 Returns:
129 Update payload message for client.
130 """
131 # Get the static url base that will form that base of our update url e.g.
132 # http://hostname:8080/static/update.gz.
133 static_urlbase = self.GetStaticUrl()
134 # Change the URL's string query dictionary provided by cherrypy to a valid
135 # dictionary that has proper values for its keys. e.g. True instead of
136 # 'True'.
137 kwargs = nebraska.QueryDictToDict(kwargs)
138
139 # Process attributes of the update check.
140 request = nebraska.Request(data)
141 if request.request_type == nebraska.Request.RequestType.EVENT:
142 _Log('A non-update event notification received. Returning an ack.')
143 return nebraska.Nebraska().GetResponseToRequest(
144 request, response_props=nebraska.ResponseProperties(**kwargs))
145
146 _Log('Update Check Received.')
147
148 try:
149 path_to_payload = self.GetPathToPayload(label, request.board)
150 base_url = _NonePathJoin(static_urlbase, path_to_payload)
151 local_payload_dir = _NonePathJoin(self.static_dir, path_to_payload)
152 except AutoupdateError as e:
153 # Raised if we fail to generate an update payload.
154 _Log('Failed to process an update request, but we will defer to '
155 'nebraska to respond with no-update. The error was %s', e)
156
157 _Log('Responding to client to use url %s to get image', base_url)
158 nebraska_props = nebraska.NebraskaProperties(
159 update_payloads_address=base_url,
160 update_metadata_dir=local_payload_dir)
161 nebraska_obj = nebraska.Nebraska(nebraska_props=nebraska_props)
162 return nebraska_obj.GetResponseToRequest(
163 request, response_props=nebraska.ResponseProperties(**kwargs))