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

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
« no previous file with comments | « no previous file | media/tools/constrained_network_server/cns_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 from logging import handlers 16 from logging import handlers
17 import mimetypes 17 import mimetypes
18 import optparse 18 import optparse
19 import os 19 import os
20 import signal 20 import signal
21 import sys 21 import sys
22 import threading 22 import threading
23 import time 23 import time
24 import urllib
25 import urllib2
26
24 import traffic_control 27 import traffic_control
25 28
26 try: 29 try:
27 import cherrypy 30 import cherrypy
28 except ImportError: 31 except ImportError:
29 print ('CNS requires CherryPy v3 or higher to be installed. Please install\n' 32 print ('CNS requires CherryPy v3 or higher to be installed. Please install\n'
30 'and try again. On Linux: sudo apt-get install python-cherrypy3\n') 33 'and try again. On Linux: sudo apt-get install python-cherrypy3\n')
31 sys.exit(1) 34 sys.exit(1)
32 35
33 # Add webm file types to mimetypes map since cherrypy's default type is text. 36 # Add webm file types to mimetypes map since cherrypy's default type is text.
(...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after
209 if no_cache: 212 if no_cache:
210 response = cherrypy.response 213 response = cherrypy.response
211 response.headers['Pragma'] = 'no-cache' 214 response.headers['Pragma'] = 'no-cache'
212 response.headers['Cache-Control'] = 'no-cache' 215 response.headers['Cache-Control'] = 'no-cache'
213 216
214 # CherryPy is a bit wonky at detecting parameters, so just make them all 217 # CherryPy is a bit wonky at detecting parameters, so just make them all
215 # optional and validate them ourselves. 218 # optional and validate them ourselves.
216 if not f: 219 if not f:
217 raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.') 220 raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.')
218 221
222 # Check existence early to prevent wasted constraint setup.
223 self._CheckRequestedFileExist(f)
224
225 # If there are no constraints, just serve the file.
226 if bandwidth is None and latency is None and loss is None:
227 return self._ServeFile(f)
228
229 constrained_port = self._GetConstrainedPort(
230 f, bandwidth=bandwidth, latency=latency, loss=loss, new_port=new_port,
231 **kwargs)
232
233 # Build constrained URL using the constrained port and original URL
234 # parameters except the network constraints (bandwidth, latency, and loss).
235 constrained_url = self._GetServerURL(f, constrained_port,
236 no_cache=no_cache, **kwargs)
237
238 # Redirect request to the constrained port.
239 cherrypy.log('Redirect to %s' % constrained_url)
240 cherrypy.lib.cptools.redirect(constrained_url, internal=False)
241
242 def _CheckRequestedFileExist(self, f):
243 """Checks if the requested file exists, raises HTTPError otherwise."""
244 if self._options.local_server_port:
245 self._CheckFileExistOnLocalServer(f)
246 else:
247 self._CheckFileExistOnServer(f)
248
249 def _CheckFileExistOnServer(self, f):
250 """Checks if requested file f exists to be served by this server."""
219 # Sanitize and check the path to prevent www-root escapes. 251 # Sanitize and check the path to prevent www-root escapes.
220 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f)) 252 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
221 if not sanitized_path.startswith(self._options.www_root): 253 if not sanitized_path.startswith(self._options.www_root):
222 raise cherrypy.HTTPError(403, 'Invalid file requested.') 254 raise cherrypy.HTTPError(403, 'Invalid file requested.')
223
224 # Check existence early to prevent wasted constraint setup.
225 if not os.path.exists(sanitized_path): 255 if not os.path.exists(sanitized_path):
226 raise cherrypy.HTTPError(404, 'File not found.') 256 raise cherrypy.HTTPError(404, 'File not found.')
227 257
228 # If there are no constraints, just serve the file. 258 def _CheckFileExistOnLocalServer(self, f):
229 if bandwidth is None and latency is None and loss is None: 259 """Checks if requested file exists on local server hosting files."""
260 test_url = self._GetServerURL(f, self._options.local_server_port)
261 try:
262 cherrypy.log('Check file exist using URL: %s' % test_url)
263 return urllib2.urlopen(test_url) is not None
264 except Exception:
265 raise cherrypy.HTTPError(404, 'File not found on local server.')
266
267 def _ServeFile(self, f):
268 """Serves the file as an http response."""
269 if self._options.local_server_port:
270 redirect_url = self._GetServerURL(f, self._options.local_server_port)
271 cherrypy.log('Redirect to %s' % redirect_url)
272 cherrypy.lib.cptools.redirect(redirect_url, internal=False)
273 else:
274 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
230 return cherrypy.lib.static.serve_file(sanitized_path) 275 return cherrypy.lib.static.serve_file(sanitized_path)
231 276
277 def _GetServerURL(self, f, port, **kwargs):
278 """Returns a URL for local server to serve the file on given port.
279
280 Args:
281 f: file name to serve on local server. Relative to www_root.
282 port: Local server port (it can be a configured constrained port).
283 kwargs: extra parameteres passed in the URL.
284 """
285 url = '%s?f=%s&' % (cherrypy.url(), f)
286 if self._options.local_server_port:
287 url = '%s/%s?' % (
288 cherrypy.url().replace('ServeConstrained', self._options.www_root), f)
289
290 url = url.replace(':%d' % self._options.port, ':%d' % port)
291 extra_args = urllib.urlencode(kwargs)
292 if extra_args:
293 url += extra_args
294 return url
295
296 def _GetConstrainedPort(self, f=None, bandwidth=None, latency=None, loss=None,
297 new_port=False, **kwargs):
298 """Creates or gets a port with specified network constraints.
299
300 See ServeConstrained() for more details.
301 """
232 # Validate inputs. isdigit() guarantees a natural number. 302 # Validate inputs. isdigit() guarantees a natural number.
233 bandwidth = self._ParseIntParameter( 303 bandwidth = self._ParseIntParameter(
234 bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0) 304 bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0)
235 latency = self._ParseIntParameter( 305 latency = self._ParseIntParameter(
236 latency, 'Invalid latency constraint.', lambda x: x >= 0) 306 latency, 'Invalid latency constraint.', lambda x: x >= 0)
237 loss = self._ParseIntParameter( 307 loss = self._ParseIntParameter(
238 loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0) 308 loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0)
239 309
310 redirect_port = self._options.port
311 if self._options.local_server_port:
312 redirect_port = self._options.local_server_port
313
314 start_time = time.time()
240 # Allocate a port using the given constraints. If a port with the requested 315 # Allocate a port using the given constraints. If a port with the requested
241 # key is already allocated, it will be reused. 316 # key and kwargs already exist then reuse that port.
242 #
243 # TODO(dalecurtis): The key cherrypy.request.remote.ip might not be unique
244 # if build slaves are sharing the same VM.
245 start_time = time.time()
246 constrained_port = self._port_allocator.Get( 317 constrained_port = self._port_allocator.Get(
247 cherrypy.request.remote.ip, server_port=self._options.port, 318 cherrypy.request.remote.ip, server_port=redirect_port,
248 interface=self._options.interface, bandwidth=bandwidth, latency=latency, 319 interface=self._options.interface, bandwidth=bandwidth, latency=latency,
249 loss=loss, new_port=new_port, file=f, **kwargs) 320 loss=loss, new_port=new_port, file=f, **kwargs)
250 end_time = time.time() 321
322 cherrypy.log('Time to set up port %d = %.3fsec.' %
323 (constrained_port, time.time() - start_time))
251 324
252 if not constrained_port: 325 if not constrained_port:
253 raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.') 326 raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.')
254 327 return constrained_port
255 cherrypy.log('Time to set up port %d = %ssec.' %
256 (constrained_port, end_time - start_time))
257
258 # Build constrained URL using the constrained port and original URL
259 # parameters except the network constraints (bandwidth, latency, and loss).
260 constrained_url = '%s?f=%s&no_cache=%s&%s' % (
261 cherrypy.url().replace(
262 ':%d' % self._options.port, ':%d' % constrained_port),
263 f,
264 no_cache,
265 '&'.join(['%s=%s' % (key, kwargs[key]) for key in kwargs]))
266
267 # Redirect request to the constrained port.
268 cherrypy.lib.cptools.redirect(constrained_url, internal=False)
269 328
270 def _ParseIntParameter(self, param, msg, check): 329 def _ParseIntParameter(self, param, msg, check):
271 """Returns integer value of param and verifies it satisfies the check. 330 """Returns integer value of param and verifies it satisfies the check.
272 331
273 Args: 332 Args:
274 param: Parameter name to check. 333 param: Parameter name to check.
275 msg: Message in error if raised. 334 msg: Message in error if raised.
276 check: Check to verify the parameter value. 335 check: Check to verify the parameter value.
277 336
278 Returns: 337 Returns:
(...skipping 30 matching lines...) Expand all
309 help=('Interface to setup constraints on. Use lo for a ' 368 help=('Interface to setup constraints on. Use lo for a '
310 'local client. Default: %default')) 369 'local client. Default: %default'))
311 parser.add_option('--socket-timeout', type='int', 370 parser.add_option('--socket-timeout', type='int',
312 default=cherrypy.server.socket_timeout, 371 default=cherrypy.server.socket_timeout,
313 help=('Number of seconds before a socket connection times ' 372 help=('Number of seconds before a socket connection times '
314 'out. Default: %default')) 373 'out. Default: %default'))
315 parser.add_option('--threads', type='int', 374 parser.add_option('--threads', type='int',
316 default=cherrypy._cpserver.Server.thread_pool, 375 default=cherrypy._cpserver.Server.thread_pool,
317 help=('Number of threads in the thread pool. Default: ' 376 help=('Number of threads in the thread pool. Default: '
318 '%default')) 377 '%default'))
319 parser.add_option('--www-root', default=os.getcwd(), 378 parser.add_option('--www-root', default='',
320 help=('Directory root to serve files from. Defaults to the ' 379 help=('Directory root to serve files from. If --local-'
321 'current directory: %default')) 380 'server-port is used, the path is appended to the '
381 'redirected URL of local server. Defaults to the '
382 'current directory (if --local-server-port is not '
383 'used): %s' % os.getcwd()))
384 parser.add_option('--local-server-port', type='int',
385 help=('Optional local server port to host files.'))
322 parser.add_option('-v', '--verbose', action='store_true', default=False, 386 parser.add_option('-v', '--verbose', action='store_true', default=False,
323 help='Turn on verbose output.') 387 help='Turn on verbose output.')
324 388
325 options = parser.parse_args()[0] 389 options = parser.parse_args()[0]
326 390
327 # Convert port range into the desired tuple format. 391 # Convert port range into the desired tuple format.
328 try: 392 try:
329 if isinstance(options.port_range, str): 393 if isinstance(options.port_range, str):
330 options.port_range = [int(port) for port in options.port_range.split(',')] 394 options.port_range = [int(port) for port in options.port_range.split(',')]
331 except ValueError: 395 except ValueError:
332 parser.error('Invalid port range specified.') 396 parser.error('Invalid port range specified.')
333 397
334 if options.expiry_time < 0: 398 if options.expiry_time < 0:
335 parser.error('Invalid expiry time specified.') 399 parser.error('Invalid expiry time specified.')
336 400
337 # Convert the path to an absolute to remove any . or .. 401 # Convert the path to an absolute to remove any . or ..
338 options.www_root = os.path.abspath(options.www_root) 402 if not options.local_server_port:
403 if not options.www_root:
404 options.www_root = os.getcwd()
405 options.www_root = os.path.abspath(options.www_root)
339 406
340 _SetLogger(options.verbose) 407 _SetLogger(options.verbose)
341 408
342 return options 409 return options
343 410
344 411
345 def _SetLogger(verbose): 412 def _SetLogger(verbose):
346 file_handler = handlers.RotatingFileHandler('cns.log', 'a', 10000000, 10) 413 file_handler = handlers.RotatingFileHandler('cns.log', 'a', 10000000, 10)
347 file_handler.setFormatter(logging.Formatter('[%(threadName)s] %(message)s')) 414 file_handler.setFormatter(logging.Formatter('[%(threadName)s] %(message)s'))
348 415
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
380 try: 447 try:
381 cherrypy.quickstart(ConstrainedNetworkServer(options, pa)) 448 cherrypy.quickstart(ConstrainedNetworkServer(options, pa))
382 finally: 449 finally:
383 # Disable Ctrl-C handler to prevent interruption of cleanup. 450 # Disable Ctrl-C handler to prevent interruption of cleanup.
384 signal.signal(signal.SIGINT, lambda signal, frame: None) 451 signal.signal(signal.SIGINT, lambda signal, frame: None)
385 pa.Cleanup(all_ports=True) 452 pa.Cleanup(all_ports=True)
386 453
387 454
388 if __name__ == '__main__': 455 if __name__ == '__main__':
389 Main() 456 Main()
OLDNEW
« no previous file with comments | « no previous file | media/tools/constrained_network_server/cns_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698