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

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 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
194 if no_cache: 197 if no_cache:
195 response = cherrypy.response 198 response = cherrypy.response
196 response.headers['Pragma'] = 'no-cache' 199 response.headers['Pragma'] = 'no-cache'
197 response.headers['Cache-Control'] = 'no-cache' 200 response.headers['Cache-Control'] = 'no-cache'
198 201
199 # CherryPy is a bit wonky at detecting parameters, so just make them all 202 # CherryPy is a bit wonky at detecting parameters, so just make them all
200 # optional and validate them ourselves. 203 # optional and validate them ourselves.
201 if not f: 204 if not f:
202 raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.') 205 raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.')
203 206
207 # Check existence early to prevent wasted constraint setup.
208 self._CheckRequestedFileExist(f)
209
210 # If there are no constraints, just serve the file.
211 if bandwidth is None and latency is None and loss is None:
212 return self._ServeFile(f)
213
214 constrained_port = self._GetConstrainedPort(
215 f, bandwidth=bandwidth, latency=latency, loss=loss, new_port=new_port,
216 **kwargs)
217
218 # Build constrained URL using the constrained port and original URL
219 # parameters except the network constraints (bandwidth, latency, and loss).
220 constrained_url = self._GetServerURL(f, constrained_port,
221 no_cache=no_cache, **kwargs)
222
223 # Redirect request to the constrained port.
224 cherrypy.log('Redirect to %s' % constrained_url)
225 cherrypy.lib.cptools.redirect(constrained_url, internal=False)
226
227 def _CheckRequestedFileExist(self, f):
228 """Checks if the requested file exists, raises HTTPError otherwise."""
229 if self._options.local_server_port:
230 self._CheckFileExistOnLocalServer(f)
231 else:
232 self._CheckFileExistOnServer(f)
233
234 def _CheckFileExistOnServer(self, f):
235 """Checks if requested file f exists to be served by this server."""
204 # Sanitize and check the path to prevent www-root escapes. 236 # Sanitize and check the path to prevent www-root escapes.
205 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f)) 237 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
206 if not sanitized_path.startswith(self._options.www_root): 238 if not sanitized_path.startswith(self._options.www_root):
207 raise cherrypy.HTTPError(403, 'Invalid file requested.') 239 raise cherrypy.HTTPError(403, 'Invalid file requested.')
208
209 # Check existence early to prevent wasted constraint setup.
210 if not os.path.exists(sanitized_path): 240 if not os.path.exists(sanitized_path):
211 raise cherrypy.HTTPError(404, 'File not found.') 241 raise cherrypy.HTTPError(404, 'File not found.')
212 242
213 # If there are no constraints, just serve the file. 243 def _CheckFileExistOnLocalServer(self, f):
214 if bandwidth is None and latency is None and loss is None: 244 """Checks if requested file exists on local server hosting files."""
245 test_url = self._GetServerURL(f, self._options.local_server_port)
246 try:
247 cherrypy.log('Check file exist using URL: %s' % test_url)
248 return urllib2.urlopen(test_url) is not None
249 except Exception:
250 raise cherrypy.HTTPError(404, 'File not found on local server.')
251
252 def _ServeFile(self, f):
253 """Serves the file as an http response."""
254 if self._options.local_server_port:
255 redirect_url = self._GetServerURL(f, self._options.local_server_port)
256 cherrypy.log('Redirect to %s' % redirect_url)
257 cherrypy.lib.cptools.redirect(redirect_url, internal=False)
258 else:
259 sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
215 return cherrypy.lib.static.serve_file(sanitized_path) 260 return cherrypy.lib.static.serve_file(sanitized_path)
216 261
262 def _GetServerURL(self, f, port, **kwargs):
263 """Returns a URL for local server to serve the file on given port.
264
265 Args:
266 f: file name to serve on local server. Relative to www_root.
267 port: Local server port (it can be a configured constrained port).
268 kwargs: extra parameteres passed in the URL.
269 """
270 base_url = '%s?f=%s' % (cherrypy.url(), f)
271 if self._options.local_server_port:
272 base_url = '%s/%s' % (cherrypy.url().replace('ServeConstrained',
DaleCurtis 2012/08/09 17:28:02 Cleaner as: base_url = '%s/%s' % ( cherrypy.u
shadi 2012/08/09 18:26:49 The www_root used with local-server-port is relati
DaleCurtis 2012/08/09 18:46:31 You need to update the --options documentation for
273 self._options.www_root),
274 f)
275 base_url = base_url.replace(':%d' % self._options.port, ':%d' % port)
276 extra_args = urllib.urlencode(kwargs)
277 if extra_args:
278 delimeter = '&'
DaleCurtis 2012/08/09 17:28:02 You can probably just roll the &, ? into the if ab
shadi 2012/08/09 18:26:49 Done.
279 if self._options.local_server_port:
280 delimeter = '?'
281 base_url += '%s%s' % (delimeter, extra_args)
282 return base_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 """
217 # Validate inputs. isdigit() guarantees a natural number. 290 # Validate inputs. isdigit() guarantees a natural number.
218 bandwidth = self._ParseIntParameter( 291 bandwidth = self._ParseIntParameter(
219 bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0) 292 bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0)
220 latency = self._ParseIntParameter( 293 latency = self._ParseIntParameter(
221 latency, 'Invalid latency constraint.', lambda x: x >= 0) 294 latency, 'Invalid latency constraint.', lambda x: x >= 0)
222 loss = self._ParseIntParameter( 295 loss = self._ParseIntParameter(
223 loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0) 296 loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0)
224 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()
225 # 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
226 # key is already allocated, it will be reused. 304 # key and kwargs already exist then reuse that port.
227 #
228 # TODO(dalecurtis): The key cherrypy.request.remote.ip might not be unique
229 # if build slaves are sharing the same VM.
230 start_time = time.time()
231 constrained_port = self._port_allocator.Get( 305 constrained_port = self._port_allocator.Get(
232 cherrypy.request.remote.ip, server_port=self._options.port, 306 cherrypy.request.remote.ip, server_port=redirect_port,
233 interface=self._options.interface, bandwidth=bandwidth, latency=latency, 307 interface=self._options.interface, bandwidth=bandwidth, latency=latency,
234 loss=loss, new_port=new_port, file=f, **kwargs) 308 loss=loss, new_port=new_port, file=f, **kwargs)
235 end_time = time.time() 309
310 cherrypy.log('Time to set up port %d = %.3fsec.' %
311 (constrained_port, time.time() - start_time))
236 312
237 if not constrained_port: 313 if not constrained_port:
238 raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.') 314 raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.')
239 315 return constrained_port
240 cherrypy.log('Time to set up port %d = %ssec.' %
241 (constrained_port, end_time - start_time))
242
243 # Build constrained URL using the constrained port and original URL
244 # parameters except the network constraints (bandwidth, latency, and loss).
245 constrained_url = '%s?f=%s&no_cache=%s&%s' % (
246 cherrypy.url().replace(
247 ':%d' % self._options.port, ':%d' % constrained_port),
248 f,
249 no_cache,
250 '&'.join(['%s=%s' % (key, kwargs[key]) for key in kwargs]))
251
252 # Redirect request to the constrained port.
253 cherrypy.lib.cptools.redirect(constrained_url, internal=False)
254 316
255 def _ParseIntParameter(self, param, msg, check): 317 def _ParseIntParameter(self, param, msg, check):
256 """Returns integer value of param and verifies it satisfies the check. 318 """Returns integer value of param and verifies it satisfies the check.
257 319
258 Args: 320 Args:
259 param: Parameter name to check. 321 param: Parameter name to check.
260 msg: Message in error if raised. 322 msg: Message in error if raised.
261 check: Check to verify the parameter value. 323 check: Check to verify the parameter value.
262 324
263 Returns: 325 Returns:
(...skipping 30 matching lines...) Expand all
294 help=('Interface to setup constraints on. Use lo for a ' 356 help=('Interface to setup constraints on. Use lo for a '
295 'local client. Default: %default')) 357 'local client. Default: %default'))
296 parser.add_option('--socket-timeout', type='int', 358 parser.add_option('--socket-timeout', type='int',
297 default=cherrypy.server.socket_timeout, 359 default=cherrypy.server.socket_timeout,
298 help=('Number of seconds before a socket connection times ' 360 help=('Number of seconds before a socket connection times '
299 'out. Default: %default')) 361 'out. Default: %default'))
300 parser.add_option('--threads', type='int', 362 parser.add_option('--threads', type='int',
301 default=cherrypy._cpserver.Server.thread_pool, 363 default=cherrypy._cpserver.Server.thread_pool,
302 help=('Number of threads in the thread pool. Default: ' 364 help=('Number of threads in the thread pool. Default: '
303 '%default')) 365 '%default'))
304 parser.add_option('--www-root', default=os.getcwd(), 366 parser.add_option('--www-root', default='',
305 help=('Directory root to serve files from. Defaults to the ' 367 help=('Directory root to serve files from. Defaults to the '
306 '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.'))
307 parser.add_option('-v', '--verbose', action='store_true', default=False, 372 parser.add_option('-v', '--verbose', action='store_true', default=False,
308 help='Turn on verbose output.') 373 help='Turn on verbose output.')
309 374
310 options = parser.parse_args()[0] 375 options = parser.parse_args()[0]
311 376
312 # Convert port range into the desired tuple format. 377 # Convert port range into the desired tuple format.
313 try: 378 try:
314 if isinstance(options.port_range, str): 379 if isinstance(options.port_range, str):
315 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(',')]
316 except ValueError: 381 except ValueError:
317 parser.error('Invalid port range specified.') 382 parser.error('Invalid port range specified.')
318 383
319 if options.expiry_time < 0: 384 if options.expiry_time < 0:
320 parser.error('Invalid expiry time specified.') 385 parser.error('Invalid expiry time specified.')
321 386
322 # Convert the path to an absolute to remove any . or .. 387 # Convert the path to an absolute to remove any . or ..
323 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)
324 392
325 _SetLogger(options.verbose) 393 _SetLogger(options.verbose)
326 394
327 return options 395 return options
328 396
329 397
330 def _SetLogger(verbose): 398 def _SetLogger(verbose):
331 file_handler = handlers.RotatingFileHandler('cns.log', 'a', 10000000, 10) 399 file_handler = handlers.RotatingFileHandler('cns.log', 'a', 10000000, 10)
332 file_handler.setFormatter(logging.Formatter('[%(threadName)s] %(message)s')) 400 file_handler.setFormatter(logging.Formatter('[%(threadName)s] %(message)s'))
333 401
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
365 try: 433 try:
366 cherrypy.quickstart(ConstrainedNetworkServer(options, pa)) 434 cherrypy.quickstart(ConstrainedNetworkServer(options, pa))
367 finally: 435 finally:
368 # Disable Ctrl-C handler to prevent interruption of cleanup. 436 # Disable Ctrl-C handler to prevent interruption of cleanup.
369 signal.signal(signal.SIGINT, lambda signal, frame: None) 437 signal.signal(signal.SIGINT, lambda signal, frame: None)
370 pa.Cleanup(all_ports=True) 438 pa.Cleanup(all_ports=True)
371 439
372 440
373 if __name__ == '__main__': 441 if __name__ == '__main__':
374 Main() 442 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