tzh
2024-08-22 c7d0944258c7d0943aa7b2211498fd612971ce27
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
 
 
import base64
import hashlib
import httplib
import json
import logging
import socket
import StringIO
import urllib2
import urlparse
 
try:
    import pycurl
except ImportError:
    pycurl = None
 
 
import common
 
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import retry
from autotest_lib.server import frontend
from autotest_lib.server import site_utils
 
 
# Give all our rpcs about six seconds of retry time. If a longer timeout
# is desired one should retry from the caller, this timeout is only meant
# to avoid uncontrolled circumstances like network flake, not, say, retry
# right across a reboot.
BASE_REQUEST_TIMEOUT = 0.1
JSON_HEADERS = {'Content-Type': 'application/json'}
RPC_EXCEPTIONS = (httplib.BadStatusLine, socket.error, urllib2.HTTPError)
MANIFEST_KEY = ('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+hlN5FB+tjCsBszmBIvI'
                'cD/djLLQm2zZfFygP4U4/o++ZM91EWtgII10LisoS47qT2TIOg4Un4+G57e'
                'lZ9PjEIhcJfANqkYrD3t9dpEzMNr936TLB2u683B5qmbB68Nq1Eel7KVc+F'
                '0BqhBondDqhvDvGPEV0vBsbErJFlNH7SQIDAQAB')
SONIC_BOARD_LABEL = 'board:sonic'
 
 
def get_extension_id(pub_key_pem=MANIFEST_KEY):
    """Computes the extension id from the public key.
 
    @param pub_key_pem: The public key used in the extension.
 
    @return: The extension id.
    """
    pub_key_der = base64.b64decode(pub_key_pem)
    sha = hashlib.sha256(pub_key_der).hexdigest()
    prefix = sha[:32]
    reencoded = ""
    ord_a = ord('a')
    for old_char in prefix:
        code = int(old_char, 16)
        new_char = chr(ord_a + code)
        reencoded += new_char
    return reencoded
 
 
class Url(object):
  """Container for URL information."""
 
  def __init__(self):
    self.scheme = 'http'
    self.netloc = ''
    self.path = ''
    self.params = ''
    self.query = ''
    self.fragment = ''
 
  def Build(self):
    """Returns the URL."""
    return urlparse.urlunparse((
        self.scheme,
        self.netloc,
        self.path,
        self.params,
        self.query,
        self.fragment))
 
 
# TODO(beeps): Move get and post to curl too, since we have the need for
# custom requests anyway.
@retry.retry(RPC_EXCEPTIONS, timeout_min=BASE_REQUEST_TIMEOUT)
def _curl_request(host, app_path, port, custom_request='', payload=None):
    """Sends a custom request throug pycurl, to the url specified.
    """
    url = Url()
    url.netloc = ':'.join((host, str(port)))
    url.path = app_path
    full_url = url.Build()
 
    response = StringIO.StringIO()
    conn = pycurl.Curl()
    conn.setopt(conn.URL, full_url)
    conn.setopt(conn.WRITEFUNCTION, response.write)
    if custom_request:
        conn.setopt(conn.CUSTOMREQUEST, custom_request)
    if payload:
        conn.setopt(conn.POSTFIELDS, payload)
    conn.perform()
    conn.close()
    return response.getvalue()
 
 
@retry.retry(RPC_EXCEPTIONS, timeout_min=BASE_REQUEST_TIMEOUT)
def _get(url):
    """Get request to the give url.
 
    @raises: Any of the retry exceptions, if we hit the timeout.
    @raises: error.TimeoutException, if the call itself times out.
        eg: a hanging urlopen will get killed with a TimeoutException while
        multiple retries that hit different Http errors will raise the last
        HttpError instead of the TimeoutException.
    """
    return urllib2.urlopen(url).read()
 
 
@retry.retry(RPC_EXCEPTIONS, timeout_min=BASE_REQUEST_TIMEOUT)
def _post(url, data):
    """Post data to the given url.
 
    @param data: Json data to post.
 
    @raises: Any of the retry exceptions, if we hit the timeout.
    @raises: error.TimeoutException, if the call itself times out.
        For examples see docstring for _get method.
    """
    request = urllib2.Request(url, json.dumps(data),
                              headers=JSON_HEADERS)
    urllib2.urlopen(request)
 
 
@retry.retry(RPC_EXCEPTIONS + (error.TestError,), timeout_min=30)
def acquire_sonic(lock_manager, additional_labels=None):
    """Lock a host that has the sonic host labels.
 
    @param lock_manager: A manager for locking/unlocking hosts, as defined by
        server.cros.host_lock_manager.
    @param additional_labels: A list of additional labels to apply in the search
        for a sonic device.
 
    @return: A string specifying the hostname of a locked sonic host.
 
    @raises ValueError: Is no hosts matching the given labels are found.
    """
    sonic_host = None
    afe = frontend.AFE(debug=True)
    labels = [SONIC_BOARD_LABEL]
    if additional_labels:
        labels += additional_labels
    sonic_hostname = utils.poll_for_condition(
            lambda: site_utils.lock_host_with_labels(afe, lock_manager, labels),
            sleep_interval=60,
            exception=SonicProxyException('Timed out trying to find a sonic '
                                          'host with labels %s.' % labels))
    logging.info('Acquired sonic host returned %s', sonic_hostname)
    return sonic_hostname
 
 
class SonicProxyException(Exception):
    """Generic exception raised when a sonic rpc fails."""
    pass
 
 
class SonicProxy(object):
    """Client capable of making calls to the sonic device server."""
    POLLING_INTERVAL = 5
    SONIC_SERVER_PORT = '8008'
 
    def __init__(self, hostname):
        """
        @param hostname: The name of the host for this sonic proxy.
        """
        self._sonic_server = 'http://%s:%s' % (hostname, self.SONIC_SERVER_PORT)
        self._hostname = hostname
 
 
    def check_server(self):
        """Checks if the sonic server is up and running.
 
        @raises: SonicProxyException if the server is unreachable.
        """
        try:
            json.loads(_get(self._sonic_server))
        except (RPC_EXCEPTIONS, error.TimeoutException) as e:
            raise SonicProxyException('Could not retrieve information about '
                                      'sonic device: %s' % e)
 
 
    def reboot(self, when="now"):
        """
        Post to the server asking for a reboot.
 
        @param when: The time till reboot. Can be any of:
            now: immediately
            fdr: set factory data reset flag and reboot now
            ota: set recovery flag and reboot now
            ota fdr: set both recovery and fdr flags, and reboot now
            ota foreground: reboot and start force update page
            idle: reboot only when idle screen usage > 10 mins
 
        @raises SonicProxyException: if we're unable to post a reboot request.
        """
        reboot_url = '%s/%s/%s' % (self._sonic_server, 'setup', 'reboot')
        reboot_params = {"params": when}
        logging.info('Rebooting device through %s.', reboot_url)
        try:
            _post(reboot_url, reboot_params)
        except (RPC_EXCEPTIONS, error.TimeoutException) as e:
            raise SonicProxyException('Could not reboot sonic device through '
                                      '%s: %s' % (self.SETUP_SERVER_PORT, e))
 
 
    def stop_app(self, app):
        """Stops the app.
 
        Performs a hard reboot if pycurl isn't available.
 
        @param app: An app name, eg YouTube, Fling, Netflix etc.
 
        @raises pycurl.error: If the DELETE request fails after retries.
        """
        if not pycurl:
            logging.warning('Rebooting sonic host to stop %s, please install '
                            'pycurl if you do not wish to reboot.', app)
            self.reboot()
            return
 
        _curl_request(self._hostname, 'apps/%s' % app,
                      self.SONIC_SERVER_PORT, 'DELETE')
 
 
    def start_app(self, app, payload):
        """Starts an app.
 
        @param app: An app name, eg YouTube, Fling, Netflix etc.
        @param payload: An url payload for the app, eg: http://www.youtube.com.
 
        @raises error.TimeoutException: If the call times out.
        """
        url = '%s/apps/%s' % (self._sonic_server, app)
        _post(url, payload)