Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(91)

Side by Side Diff: media/tools/constrained_network_server/cns.py

Issue 10824173: Enable CNS to serve files from different port. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Constrained Network Server. Serves files with supplied network constraints. 6 """Constrained Network Server. Serves files with supplied network constraints.
7 7
8 The CNS exposes a web based API allowing network constraints to be imposed on 8 The CNS exposes a web based API allowing network constraints to be imposed on
9 file serving. 9 file serving.
10 10
11 TODO(dalecurtis): Add some more docs here. 11 TODO(dalecurtis): Add some more docs here.
12 12
13 """ 13 """
14 14
15 import logging 15 import logging
16 import mimetypes 16 import mimetypes
17 import optparse 17 import optparse
18 import os 18 import os
19 import signal 19 import signal
20 import sys 20 import sys
21 import threading 21 import threading
22 import time 22 import time
23 import urllib2
24
23 import traffic_control 25 import traffic_control
24 26
25 try: 27 try:
26 import cherrypy 28 import cherrypy
27 except ImportError: 29 except ImportError:
28 print ('CNS requires CherryPy v3 or higher to be installed. Please install\n' 30 print ('CNS requires CherryPy v3 or higher to be installed. Please install\n'
29 'and try again. On Linux: sudo apt-get install python-cherrypy3\n') 31 'and try again. On Linux: sudo apt-get install python-cherrypy3\n')
30 sys.exit(1) 32 sys.exit(1)
31 33
32 # Add webm file types to mimetypes map since cherrypy's default type is text. 34 # Add webm file types to mimetypes map since cherrypy's default type is text.
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 if no_cache: 195 if no_cache:
194 response = cherrypy.response 196 response = cherrypy.response
195 response.headers['Pragma'] = 'no-cache' 197 response.headers['Pragma'] = 'no-cache'
196 response.headers['Cache-Control'] = 'no-cache' 198 response.headers['Cache-Control'] = 'no-cache'
197 199
198 # CherryPy is a bit wonky at detecting parameters, so just make them all 200 # CherryPy is a bit wonky at detecting parameters, so just make them all
199 # optional and validate them ourselves. 201 # optional and validate them ourselves.
200 if not f: 202 if not f:
201 raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.') 203 raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.')
202 204
205 # Check existence early to prevent wasted constraint setup.
206 self._CheckRequestedFileExist(f)
207
208 # If there are no constraints, just serve the file.
209 if bandwidth is None and latency is None and loss is None:
210 return self._ServeFile(f)
211
212 constrained_port = self._GetConstrainedPort(
213 f, bandwidth=bandwidth, latency=latency, loss=loss, new_port=new_port,
214 **kwargs)
215
216 # Build constrained URL using the constrained port and original URL
217 # parameters except the network constraints (bandwidth, latency, and loss).
218 if self._options.local_server_port:
219 constrained_url = self._GetLocalServerURL(f, constrained_port, **kwargs)
220 else:
221 constrained_url = '%s?f=%s&no_cache=%s&%s' % (
DaleCurtis 2012/08/07 23:53:57 This looks similar to the self._GetLocalServerURL(
shadi 2012/08/08 22:55:49 They are not actually that similar, but Done. :-)
222 cherrypy.url().replace(
223 ':%d' % self._options.port, ':%d' % constrained_port),
224 '%s' % f,
225 no_cache,
226 '&'.join(['%s=%s' % (key, kwargs[key]) for key in kwargs]))
227
228 # Redirect request to the constrained port.
229 cherrypy.log('Redirect to %s' % constrained_url)
230 cherrypy.lib.cptools.redirect(constrained_url, internal=False)
231
232 def _CheckRequestedFileExist(self, f):
233 """Checks if the requested file exists, raises HTTPError otherwise."""
234 if self._options.local_server_port:
235 self._CheckFileExistOnLocalServer(f)
236 else:
237 self._CheckFileExistOnServer(f)
238
239 def _CheckFileExistOnServer(self, f):
240 """Checks if requested f exists to be served by this server."""
DaleCurtis 2012/08/07 23:53:57 s/f/file f/
shadi 2012/08/08 22:55:49 Done.
203 # Sanitize and check the path to prevent www-root escapes. 241 # Sanitize and check the path to prevent www-root escapes.
204 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f)) 242 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
205 if not sanitized_path.startswith(self._options.www_root): 243 if not sanitized_path.startswith(self._options.www_root):
206 raise cherrypy.HTTPError(403, 'Invalid file requested.') 244 raise cherrypy.HTTPError(403, 'Invalid file requested.')
207
208 # Check existence early to prevent wasted constraint setup.
209 if not os.path.exists(sanitized_path): 245 if not os.path.exists(sanitized_path):
210 raise cherrypy.HTTPError(404, 'File not found.') 246 raise cherrypy.HTTPError(404, 'File not found.')
211 247
212 # If there are no constraints, just serve the file. 248 def _CheckFileExistOnLocalServer(self, f):
DaleCurtis 2012/08/07 23:53:57 Hmmm, what is urlopen doing behind the scenes here
shadi 2012/08/08 22:55:49 No it is not pulling down the remote file. It retu
213 if bandwidth is None and latency is None and loss is None: 249 """Checks if requested file exists on local server hosting files."""
250 test_url = self._GetLocalServerURL(f, self._options.local_server_port)
251 try:
252 cherrypy.log('Check file exist using URL: %s' % test_url)
253 return urllib2.urlopen(test_url) is not None
254 except Exception:
255 raise cherrypy.HTTPError(404, 'File not found on local server.')
256
257 def _ServeFile(self, f):
258 """Serves the file as an http response."""
259 if self._options.local_server_port:
260 redirect_url = self._GetLocalServerURL(f, self._options.local_server_port)
261 cherrypy.log('Redirect to %s' % redirect_url)
262 cherrypy.lib.cptools.redirect(redirect_url, internal=False)
DaleCurtis 2012/08/07 23:53:57 Do you need to return anything here to keep Cherry
shadi 2012/08/08 22:55:49 CherryPy has not been complaining :-)
263 else:
264 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
214 return cherrypy.lib.static.serve_file(sanitized_path) 265 return cherrypy.lib.static.serve_file(sanitized_path)
215 266
267 def _GetLocalServerURL(self, f, port, **kwargs):
268 """Returns a URL for local server to serve the file on given port.
269
270 Args:
271 f: file name to serve on local server. Relative to www_root.
272 port: Local server port (it can be a configured constrained port).
273 kwargs: extra parameteres passed in the URL.
274 """
275 base_url = cherrypy.url().replace('ServeConstrained',
276 self._options.www_root)
277 base_url = base_url.replace(':%d' % self._options.port, ':%d' % port)
278 url = '%s/%s' % (base_url, f)
279 extra_args = '&'.join(['%s=%s' % (key, kwargs[key]) for key in kwargs])
280 if extra_args:
281 url += '?%s' % extra_args
282 return url
283
284 def _GetConstrainedPort(self, f=None, bandwidth=None, latency=None, loss=None,
285 new_port=False, **kwargs):
286 """Creates or gets a port with specified network constraints.
287
288 See ServeConstrained() for more details.
289 """
216 # Validate inputs. isdigit() guarantees a natural number. 290 # Validate inputs. isdigit() guarantees a natural number.
217 bandwidth = self._ParseIntParameter( 291 bandwidth = self._ParseIntParameter(
218 bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0) 292 bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0)
219 latency = self._ParseIntParameter( 293 latency = self._ParseIntParameter(
220 latency, 'Invalid latency constraint.', lambda x: x >= 0) 294 latency, 'Invalid latency constraint.', lambda x: x >= 0)
221 loss = self._ParseIntParameter( 295 loss = self._ParseIntParameter(
222 loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0) 296 loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0)
223 297
298 redirect_port = self._options.port
299 if self._options.local_server_port:
300 redirect_port = self._options.local_server_port
301
302 start_time = time.time()
224 # Allocate a port using the given constraints. If a port with the requested 303 # Allocate a port using the given constraints. If a port with the requested
225 # key is already allocated, it will be reused. 304 # key and kwargs already exist then reuse that port.
226 #
227 # TODO(dalecurtis): The key cherrypy.request.remote.ip might not be unique
228 # if build slaves are sharing the same VM.
229 start_time = time.time()
230 constrained_port = self._port_allocator.Get( 305 constrained_port = self._port_allocator.Get(
231 cherrypy.request.remote.ip, server_port=self._options.port, 306 cherrypy.request.remote.ip, server_port=redirect_port,
232 interface=self._options.interface, bandwidth=bandwidth, latency=latency, 307 interface=self._options.interface, bandwidth=bandwidth, latency=latency,
233 loss=loss, new_port=new_port, file=f, **kwargs) 308 loss=loss, new_port=new_port, file=f, **kwargs)
234 end_time = time.time() 309
310 cherrypy.log('Time to set up port %d = %ssec.' %
311 (constrained_port, time.time() - start_time))
235 312
236 if not constrained_port: 313 if not constrained_port:
237 raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.') 314 raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.')
238 315 return constrained_port
239 cherrypy.log('Time to set up port %d = %ssec.' %
240 (constrained_port, end_time - start_time))
241
242 # Build constrained URL using the constrained port and original URL
243 # parameters except the network constraints (bandwidth, latency, and loss).
244 constrained_url = '%s?f=%s&no_cache=%s&%s' % (
245 cherrypy.url().replace(
246 ':%d' % self._options.port, ':%d' % constrained_port),
247 f,
248 no_cache,
249 '&'.join(['%s=%s' % (key, kwargs[key]) for key in kwargs]))
250
251 # Redirect request to the constrained port.
252 cherrypy.lib.cptools.redirect(constrained_url, internal=False)
253 316
254 def _ParseIntParameter(self, param, msg, check): 317 def _ParseIntParameter(self, param, msg, check):
255 """Returns integer value of param and verifies it satisfies the check. 318 """Returns integer value of param and verifies it satisfies the check.
256 319
257 Args: 320 Args:
258 param: Parameter name to check. 321 param: Parameter name to check.
259 msg: Message in error if raised. 322 msg: Message in error if raised.
260 check: Check to verify the parameter value. 323 check: Check to verify the parameter value.
261 324
262 Returns: 325 Returns:
(...skipping 30 matching lines...) Expand all
293 help=('Interface to setup constraints on. Use lo for a ' 356 help=('Interface to setup constraints on. Use lo for a '
294 'local client. Default: %default')) 357 'local client. Default: %default'))
295 parser.add_option('--socket-timeout', type='int', 358 parser.add_option('--socket-timeout', type='int',
296 default=cherrypy.server.socket_timeout, 359 default=cherrypy.server.socket_timeout,
297 help=('Number of seconds before a socket connection times ' 360 help=('Number of seconds before a socket connection times '
298 'out. Default: %default')) 361 'out. Default: %default'))
299 parser.add_option('--threads', type='int', 362 parser.add_option('--threads', type='int',
300 default=cherrypy._cpserver.Server.thread_pool, 363 default=cherrypy._cpserver.Server.thread_pool,
301 help=('Number of threads in the thread pool. Default: ' 364 help=('Number of threads in the thread pool. Default: '
302 '%default')) 365 '%default'))
303 parser.add_option('--www-root', default=os.getcwd(), 366 parser.add_option('--www-root', default='',
DaleCurtis 2012/08/07 23:53:57 just remove the default='' ?
shadi 2012/08/08 22:55:49 I actually put this so that www_root == '' when --
304 help=('Directory root to serve files from. Defaults to the ' 367 help=('Directory root to serve files from. Defaults to the '
305 'current directory: %default')) 368 'current directory (if --local-server-port is not '
369 'used): %s' % os.getcwd()))
370 parser.add_option('--local-server-port', type='int',
371 help=('Optional local server port to host files.'))
306 parser.add_option('-v', '--verbose', action='store_true', default=False, 372 parser.add_option('-v', '--verbose', action='store_true', default=False,
307 help='Turn on verbose output.') 373 help='Turn on verbose output.')
308 374
309 options = parser.parse_args()[0] 375 options = parser.parse_args()[0]
310 376
311 # Convert port range into the desired tuple format. 377 # Convert port range into the desired tuple format.
312 try: 378 try:
313 if isinstance(options.port_range, str): 379 if isinstance(options.port_range, str):
314 options.port_range = [int(port) for port in options.port_range.split(',')] 380 options.port_range = [int(port) for port in options.port_range.split(',')]
315 except ValueError: 381 except ValueError:
316 parser.error('Invalid port range specified.') 382 parser.error('Invalid port range specified.')
317 383
318 if options.expiry_time < 0: 384 if options.expiry_time < 0:
319 parser.error('Invalid expiry time specified.') 385 parser.error('Invalid expiry time specified.')
320 386
321 # Convert the path to an absolute to remove any . or .. 387 # Convert the path to an absolute to remove any . or ..
322 options.www_root = os.path.abspath(options.www_root) 388 if not options.local_server_port:
389 if not options.www_root:
390 options.www_root = os.getcwd()
391 options.www_root = os.path.abspath(options.www_root)
323 392
324 # Required so that cherrypy logs do not get propagated to root logger causing 393 # Required so that cherrypy logs do not get propagated to root logger causing
325 # the logs to be printed twice. 394 # the logs to be printed twice.
326 cherrypy.log.error_log.propagate = False 395 cherrypy.log.error_log.propagate = False
327 cherrypy.log.access_log.propagate = False 396 cherrypy.log.access_log.propagate = False
328 397
329 _SetLogger(options.verbose) 398 _SetLogger(options.verbose)
330 399
331 return options 400 return options
332 401
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 try: 433 try:
365 cherrypy.quickstart(ConstrainedNetworkServer(options, pa)) 434 cherrypy.quickstart(ConstrainedNetworkServer(options, pa))
366 finally: 435 finally:
367 # Disable Ctrl-C handler to prevent interruption of cleanup. 436 # Disable Ctrl-C handler to prevent interruption of cleanup.
368 signal.signal(signal.SIGINT, lambda signal, frame: None) 437 signal.signal(signal.SIGINT, lambda signal, frame: None)
369 pa.Cleanup(all_ports=True) 438 pa.Cleanup(all_ports=True)
370 439
371 440
372 if __name__ == '__main__': 441 if __name__ == '__main__':
373 Main() 442 Main()
OLDNEW
« no previous file with comments | « no previous file | media/tools/constrained_network_server/cns_test.py » ('j') | media/tools/constrained_network_server/cns_test.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698