15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Constrained Network Server. Serves files with supplied network constraints.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The CNS exposes a web based API allowing network constraints to be imposed on
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)file serving.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TODO(dalecurtis): Add some more docs here.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from logging import handlers
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import mimetypes
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import signal
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import threading
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib2
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import traffic_control
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  import cherrypy
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  print ('CNS requires CherryPy v3 or higher to be installed. Please install\n'
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         'and try again. On Linux: sudo apt-get install python-cherrypy3\n')
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(1)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Add webm file types to mimetypes map since cherrypy's default type is text.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)mimetypes.types_map['.webm'] = 'video/webm'
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Default logging is ERROR. Use --verbose to enable DEBUG logging.
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_DEFAULT_LOG_LEVEL = logging.ERROR
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Default port to serve the CNS on.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_DEFAULT_SERVING_PORT = 9000
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Default port range for constrained use.
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_DEFAULT_CNS_PORT_RANGE = (50000, 51000)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Default number of seconds before a port can be torn down.
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_DEFAULT_PORT_EXPIRY_TIME_SECS = 5 * 60
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class PortAllocator(object):
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Dynamically allocates/deallocates ports with a given set of constraints."""
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, port_range, expiry_time_secs=5 * 60):
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Sets up initial state for the Port Allocator.
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      port_range: Range of ports available for allocation.
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      expiry_time_secs: Amount of time in seconds before constrained ports are
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cleaned up.
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._port_range = port_range
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._expiry_time_secs = expiry_time_secs
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Keeps track of ports we've used, the creation key, and the last request
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # time for the port so they can be cached and cleaned up later.
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._ports = {}
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Locks port creation and cleanup. TODO(dalecurtis): If performance becomes
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # an issue a per-port based lock system can be used instead.
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._port_lock = threading.RLock()
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Get(self, key, new_port=False, **kwargs):
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Sets up a constrained port using the requested parameters.
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Requests for the same key and constraints will result in a cached port being
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    returned if possible, subject to new_port.
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      key: Used to cache ports with the given constraints.
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new_port: Whether to create a new port or use an existing one if possible.
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      **kwargs: Constraints to pass into traffic control.
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      None if no port can be setup or the port number of the constrained port.
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    with self._port_lock:
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Check port key cache to see if this port is already setup. Update the
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # cache time and return the port if so. Performance isn't a concern here,
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # so just iterate over ports dict for simplicity.
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      full_key = (key,) + tuple(kwargs.values())
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not new_port:
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for port, status in self._ports.iteritems():
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if full_key == status['key']:
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self._ports[port]['last_update'] = time.time()
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return port
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Cleanup ports on new port requests. Do it after the cache check though
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # so we don't erase and then setup the same port.
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self._expiry_time_secs > 0:
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.Cleanup(all_ports=False)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Performance isn't really an issue here, so just iterate over the port
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # range to find an unused port. If no port is found, None is returned.
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for port in xrange(self._port_range[0], self._port_range[1]):
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if port in self._ports:
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          continue
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self._SetupPort(port, **kwargs):
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          kwargs['port'] = port
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          self._ports[port] = {'last_update': time.time(), 'key': full_key,
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               'config': kwargs}
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          return port
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _SetupPort(self, port, **kwargs):
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Setup network constraints on port using the requested parameters.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      port: The port number to setup network constraints on.
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      **kwargs: Network constraints to set up on the port.
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      True if setting the network constraints on the port was successful, false
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      otherwise.
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    kwargs['port'] = port
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.log('Setting up port %d' % port)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      traffic_control.CreateConstrainedPort(kwargs)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return True
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except traffic_control.TrafficControlError as e:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.log('Error: %s\nOutput: %s' % (e.msg, e.error))
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return False
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Cleanup(self, all_ports, request_ip=None):
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Cleans up expired ports, or if all_ports=True, all allocated ports.
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    By default, ports which haven't been used for self._expiry_time_secs are
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    torn down. If all_ports=True then they are torn down regardless.
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      all_ports: Should all ports be torn down regardless of expiration?
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      request_ip: Tear ports matching the IP address regarless of expiration.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    with self._port_lock:
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      now = time.time()
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Use .items() instead of .iteritems() so we can delete keys w/o error.
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for port, status in self._ports.items():
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        expired = now - status['last_update'] > self._expiry_time_secs
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        matching_ip = request_ip and status['key'][0].startswith(request_ip)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if all_ports or expired or matching_ip:
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cherrypy.log('Cleaning up port %d' % port)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          self._DeletePort(port)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          del self._ports[port]
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _DeletePort(self, port):
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Deletes network constraints on port.
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      port: The port number associated with the network constraints.
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      traffic_control.DeleteConstrainedPort(self._ports[port]['config'])
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except traffic_control.TrafficControlError as e:
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.log('Error: %s\nOutput: %s' % (e.msg, e.error))
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ConstrainedNetworkServer(object):
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A CherryPy-based HTTP server for serving files with network constraints."""
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, options, port_allocator):
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Sets up initial state for the CNS.
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      options: optparse based class returned by ParseArgs()
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      port_allocator: A port allocator instance.
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._options = options
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._port_allocator = port_allocator
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @cherrypy.expose
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Cleanup(self):
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Cleans up all the ports allocated using the request IP address.
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    When requesting a constrained port, the cherrypy.request.remote.ip is used
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    as a key for that port (in addition to other request parameters).  Such
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ports created for the same IP address are removed.
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.log('Cleaning up ports allocated by %s.' %
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 cherrypy.request.remote.ip)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._port_allocator.Cleanup(all_ports=False,
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 request_ip=cherrypy.request.remote.ip)
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  @cherrypy.expose
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ServeConstrained(self, f=None, bandwidth=None, latency=None, loss=None,
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       new_port=False, no_cache=False, **kwargs):
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Serves the requested file with the requested constraints.
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Subsequent requests for the same constraints from the same IP will share the
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    previously created port unless new_port equals True. If no constraints
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    are provided the file is served as is.
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f: path relative to http root of file to serve.
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      bandwidth: maximum allowed bandwidth for the provided port (integer
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          in kbit/s).
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      latency: time to add to each packet (integer in ms).
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      loss: percentage of packets to drop (integer, 0-100).
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new_port: whether to use a new port for this request or not.
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      no_cache: Set reponse's cache-control to no-cache.
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if no_cache:
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response = cherrypy.response
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.headers['Pragma'] = 'no-cache'
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      response.headers['Cache-Control'] = 'no-cache'
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # CherryPy is a bit wonky at detecting parameters, so just make them all
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # optional and validate them ourselves.
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not f:
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise cherrypy.HTTPError(400, 'Invalid request. File must be specified.')
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Check existence early to prevent wasted constraint setup.
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._CheckRequestedFileExist(f)
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If there are no constraints, just serve the file.
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if bandwidth is None and latency is None and loss is None:
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return self._ServeFile(f)
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    constrained_port = self._GetConstrainedPort(
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        f, bandwidth=bandwidth, latency=latency, loss=loss, new_port=new_port,
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        **kwargs)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Build constrained URL using the constrained port and original URL
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # parameters except the network constraints (bandwidth, latency, and loss).
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    constrained_url = self._GetServerURL(f, constrained_port,
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         no_cache=no_cache, **kwargs)
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Redirect request to the constrained port.
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.log('Redirect to %s' % constrained_url)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.lib.cptools.redirect(constrained_url, internal=False)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CheckRequestedFileExist(self, f):
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Checks if the requested file exists, raises HTTPError otherwise."""
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._options.local_server_port:
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._CheckFileExistOnLocalServer(f)
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self._CheckFileExistOnServer(f)
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CheckFileExistOnServer(self, f):
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Checks if requested file f exists to be served by this server."""
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Sanitize and check the path to prevent www-root escapes.
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not sanitized_path.startswith(self._options.www_root):
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise cherrypy.HTTPError(403, 'Invalid file requested.')
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not os.path.exists(sanitized_path):
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise cherrypy.HTTPError(404, 'File not found.')
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _CheckFileExistOnLocalServer(self, f):
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Checks if requested file exists on local server hosting files."""
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test_url = self._GetServerURL(f, self._options.local_server_port)
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.log('Check file exist using URL: %s' % test_url)
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return urllib2.urlopen(test_url) is not None
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except Exception:
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise cherrypy.HTTPError(404, 'File not found on local server.')
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ServeFile(self, f):
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Serves the file as an http response."""
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._options.local_server_port:
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      redirect_url = self._GetServerURL(f, self._options.local_server_port)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.log('Redirect to %s' % redirect_url)
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.lib.cptools.redirect(redirect_url, internal=False)
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      sanitized_path = os.path.abspath(os.path.join(self._options.www_root, f))
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return cherrypy.lib.static.serve_file(sanitized_path)
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _GetServerURL(self, f, port, **kwargs):
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns a URL for local server to serve the file on given port.
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      f: file name to serve on local server. Relative to www_root.
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      port: Local server port (it can be a configured constrained port).
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      kwargs: extra parameteres passed in the URL.
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    url = '%s?f=%s&' % (cherrypy.url(), f)
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._options.local_server_port:
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      url = '%s/%s?' % (
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cherrypy.url().replace('ServeConstrained', self._options.www_root), f)
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    url = url.replace(':%d' % self._options.port, ':%d' % port)
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    extra_args = urllib.urlencode(kwargs)
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if extra_args:
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      url += extra_args
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return url
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _GetConstrainedPort(self, f=None, bandwidth=None, latency=None, loss=None,
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          new_port=False, **kwargs):
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Creates or gets a port with specified network constraints.
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    See ServeConstrained() for more details.
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Validate inputs. isdigit() guarantees a natural number.
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    bandwidth = self._ParseIntParameter(
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        bandwidth, 'Invalid bandwidth constraint.', lambda x: x > 0)
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    latency = self._ParseIntParameter(
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        latency, 'Invalid latency constraint.', lambda x: x >= 0)
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    loss = self._ParseIntParameter(
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        loss, 'Invalid loss constraint.', lambda x: x <= 100 and x >= 0)
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    redirect_port = self._options.port
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self._options.local_server_port:
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      redirect_port = self._options.local_server_port
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    start_time = time.time()
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Allocate a port using the given constraints. If a port with the requested
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # key and kwargs already exist then reuse that port.
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    constrained_port = self._port_allocator.Get(
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cherrypy.request.remote.ip, server_port=redirect_port,
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        interface=self._options.interface, bandwidth=bandwidth, latency=latency,
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        loss=loss, new_port=new_port, file=f, **kwargs)
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.log('Time to set up port %d = %.3fsec.' %
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 (constrained_port, time.time() - start_time))
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not constrained_port:
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise cherrypy.HTTPError(503, 'Service unavailable. Out of ports.')
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return constrained_port
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def _ParseIntParameter(self, param, msg, check):
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns integer value of param and verifies it satisfies the check.
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      param: Parameter name to check.
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      msg: Message in error if raised.
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      check: Check to verify the parameter value.
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      None if param is None, integer value of param otherwise.
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cherrypy.HTTPError if param can not be converted to integer or if it does
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      not satisfy the check.
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if param:
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        int_value = int(param)
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if check(int_value):
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          return int_value
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except:
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pass
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise cherrypy.HTTPError(400, msg)
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ParseArgs():
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Define and parse the command-line arguments."""
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser = optparse.OptionParser()
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--expiry-time', type='int',
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    default=_DEFAULT_PORT_EXPIRY_TIME_SECS,
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Number of seconds before constrained ports expire '
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'and are cleaned up. 0=Disabled. Default: %default'))
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--port', type='int', default=_DEFAULT_SERVING_PORT,
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help='Port to serve the API on. Default: %default')
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--port-range', default=_DEFAULT_CNS_PORT_RANGE,
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Range of ports for constrained serving. Specify as '
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'a comma separated value pair. Default: %default'))
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--interface', default='eth0',
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Interface to setup constraints on. Use lo for a '
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'local client. Default: %default'))
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--socket-timeout', type='int',
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    default=cherrypy.server.socket_timeout,
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Number of seconds before a socket connection times '
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'out. Default: %default'))
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--threads', type='int',
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    default=cherrypy._cpserver.Server.thread_pool,
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Number of threads in the thread pool. Default: '
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          '%default'))
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--www-root', default='',
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Directory root to serve files from. If --local-'
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'server-port is used, the path is appended to the '
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'redirected URL of local server. Defaults to the '
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'current directory (if --local-server-port is not '
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'used): %s' % os.getcwd()))
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--local-server-port', type='int',
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Optional local server port to host files.'))
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('-v', '--verbose', action='store_true', default=False,
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help='Turn on verbose output.')
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  options = parser.parse_args()[0]
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Convert port range into the desired tuple format.
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if isinstance(options.port_range, str):
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      options.port_range = [int(port) for port in options.port_range.split(',')]
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except ValueError:
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parser.error('Invalid port range specified.')
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.expiry_time < 0:
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parser.error('Invalid expiry time specified.')
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Convert the path to an absolute to remove any . or ..
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not options.local_server_port:
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not options.www_root:
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      options.www_root = os.getcwd()
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    options.www_root = os.path.abspath(options.www_root)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _SetLogger(options.verbose)
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return options
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _SetLogger(verbose):
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  file_handler = handlers.RotatingFileHandler('cns.log', 'a', 10000000, 10)
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  file_handler.setFormatter(logging.Formatter('[%(threadName)s] %(message)s'))
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  log_level = _DEFAULT_LOG_LEVEL
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if verbose:
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    log_level = logging.DEBUG
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  file_handler.setLevel(log_level)
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cherrypy.log.error_log.addHandler(file_handler)
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cherrypy.log.access_log.addHandler(file_handler)
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Main():
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Configure and start the ConstrainedNetworkServer."""
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  options = ParseArgs()
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    traffic_control.CheckRequirements()
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except traffic_control.TrafficControlError as e:
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.log(e.msg)
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cherrypy.config.update({'server.socket_host': '::',
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'server.socket_port': options.port})
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.threads:
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.config.update({'server.thread_pool': options.threads})
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if options.socket_timeout:
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.config.update({'server.socket_timeout': options.socket_timeout})
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Setup port allocator here so we can call cleanup on failures/exit.
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pa = PortAllocator(options.port_range, expiry_time_secs=options.expiry_time)
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cherrypy.quickstart(ConstrainedNetworkServer(options, pa))
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  finally:
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Disable Ctrl-C handler to prevent interruption of cleanup.
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    signal.signal(signal.SIGINT, lambda signal, frame: None)
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pa.Cleanup(all_ports=True)
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Main()
457