blob: 001959b3a2a1d988b2c0b3858f157e8e2df0af01 [file] [log] [blame]
Amin Hassani2aa34282020-11-18 01:18:191# -*- 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:1927
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:1972 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
Amin Hassani624b7ae2020-12-04 18:55:4994 def GetBuildID(self, label, board):
95 """Find the build id of the given lable and board.
Amin Hassani2aa34282020-11-18 01:18:1996
97 Args:
98 label: from update request
99 board: from update request
100
101 Returns:
Amin Hassani624b7ae2020-12-04 18:55:49102 The build id of given label and board. e.g. reef-release/R88-13591.0.0
Amin Hassani2aa34282020-11-18 01:18:19103
104 Raises:
105 AutoupdateError: If the update could not be found.
106 """
107 label = label or ''
108 label_list = label.split('/')
Amin Hassani8b503582020-12-03 04:47:01109 if label_list[0] == 'xbuddy':
110 # If path explicitly calls xbuddy, pop off the tag.
111 label_list.pop()
112 x_label, _ = self.xbuddy.Translate(label_list, board=board)
Amin Hassani624b7ae2020-12-04 18:55:49113 return x_label
Amin Hassani2aa34282020-11-18 01:18:19114
115 def HandleUpdatePing(self, data, label='', **kwargs):
116 """Handles an update ping from an update client.
117
118 Args:
119 data: XML blob from client.
120 label: optional label for the update.
121 kwargs: The map of query strings passed to the /update API.
122
123 Returns:
124 Update payload message for client.
125 """
Amin Hassani2aa34282020-11-18 01:18:19126 # Change the URL's string query dictionary provided by cherrypy to a valid
127 # dictionary that has proper values for its keys. e.g. True instead of
128 # 'True'.
129 kwargs = nebraska.QueryDictToDict(kwargs)
130
131 # Process attributes of the update check.
132 request = nebraska.Request(data)
133 if request.request_type == nebraska.Request.RequestType.EVENT:
134 _Log('A non-update event notification received. Returning an ack.')
135 return nebraska.Nebraska().GetResponseToRequest(
136 request, response_props=nebraska.ResponseProperties(**kwargs))
137
138 _Log('Update Check Received.')
139
140 try:
Amin Hassani624b7ae2020-12-04 18:55:49141 build_id = self.GetBuildID(label, request.board)
142 base_url = '/'.join((self.GetStaticUrl(), build_id))
143 local_payload_dir = _NonePathJoin(self.static_dir, build_id)
Amin Hassani2aa34282020-11-18 01:18:19144 except AutoupdateError as e:
145 # Raised if we fail to generate an update payload.
146 _Log('Failed to process an update request, but we will defer to '
147 'nebraska to respond with no-update. The error was %s', e)
148
149 _Log('Responding to client to use url %s to get image', base_url)
150 nebraska_props = nebraska.NebraskaProperties(
151 update_payloads_address=base_url,
152 update_metadata_dir=local_payload_dir)
153 nebraska_obj = nebraska.Nebraska(nebraska_props=nebraska_props)
154 return nebraska_obj.GetResponseToRequest(
155 request, response_props=nebraska.ResponseProperties(**kwargs))