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