"""Module containing methods and classes to interact with a devserver instance.
"""
+from __future__ import print_function
+
import logging
import multiprocessing
import os
import socket
import tempfile
+import httplib
import urllib2
-from chromite.buildbot import constants
+from chromite.cbuildbot import constants
from chromite.lib import cros_build_lib
from chromite.lib import osutils
from chromite.lib import timeout_util
from chromite.lib import remote_access
+DEFAULT_PORT = 8080
+
+
def GenerateUpdateId(target, src, key, for_vm):
"""Returns a simple representation id of |target| and |src| paths.
"""Thrown when the devserver fails to start up."""
+class DevServerStopError(DevServerException):
+ """Thrown when the devserver fails to stop."""
+
+
class DevServerResponseError(DevServerException):
"""Thrown when the devserver responds with an error."""
"""
super(DevServerWrapper, self).__init__()
self.devserver_bin = 'start_devserver'
- self.port = 8080 if not port else port
+ # Set port if it is given. Otherwise, devserver will start at any
+ # available port.
+ self.port = None if not port else port
self.src_image = src_image
self.board = board
self.tempdir = None
sudo_rm=True)
self.log_dir = self.tempdir.tempdir
self.static_dir = static_dir
- self.log_filename = os.path.join(self.log_dir, 'dev_server.log')
+ self.log_file = os.path.join(self.log_dir, 'dev_server.log')
+ self.port_file = os.path.join(self.log_dir, 'dev_server.port')
self._pid_file = self._GetPIDFilePath()
self._pid = None
logging.info('Downloading %s to %s', url, dest)
osutils.WriteFile(dest, DevServerWrapper.OpenURL(url), mode='wb')
+ def GetURL(self, sub_dir=None):
+ """Returns the URL of this devserver instance."""
+ return self.GetDevServerURL(port=self.port, sub_dir=sub_dir)
+
@classmethod
def GetDevServerURL(cls, ip=None, port=None, sub_dir=None):
"""Returns the dev server url.
sub_dir: The subdirectory of the devserver url.
"""
ip = cros_build_lib.GetIPv4Address() if not ip else ip
- port = 8080 if not port else port
+ # If port number is not given, assume 8080 for backward
+ # compatibility.
+ port = DEFAULT_PORT if not port else port
url = 'http://%(ip)s:%(port)s' % {'ip': ip, 'port': str(port)}
if sub_dir:
url += '/' + sub_dir
logging.debug('Retrieving %s', url)
try:
res = urllib2.urlopen(url, timeout=timeout)
- except urllib2.HTTPError as e:
- logging.error('Devserver responsded with an error!')
+ except (urllib2.HTTPError, httplib.HTTPException) as e:
+ logging.error('Devserver responded with an error!')
raise DevServerResponseError(e)
except (urllib2.URLError, socket.timeout) as e:
if not ignore_url_error:
cmd, enter_chroot=True, print_cmd=False, combine_stdout_stderr=True,
redirect_stdout=True, redirect_stderr=True, cwd=constants.SOURCE_ROOT)
+ def _ReadPortNumber(self):
+ """Read port number from file."""
+ if not self.is_alive():
+ raise DevServerStartupError('Devserver terminated unexpectedly!')
+
+ try:
+ timeout_util.WaitForReturnTrue(os.path.exists,
+ func_args=[self.port_file],
+ timeout=self.DEV_SERVER_TIMEOUT,
+ period=5)
+ except timeout_util.TimeoutError:
+ self.terminate()
+ raise DevServerStartupError('Devserver portfile does not exist!')
+
+ self.port = int(osutils.ReadFile(self.port_file).strip())
+
def IsReady(self):
"""Check if devserver is up and running."""
if not self.is_alive():
def _WaitUntilStarted(self):
"""Wait until the devserver has started."""
+ if not self.port:
+ self._ReadPortNumber()
+
try:
- timeout_util.WaitForReturnValue([True], self.IsReady,
- timeout=self.DEV_SERVER_TIMEOUT,
- period=5)
+ timeout_util.WaitForReturnTrue(self.IsReady,
+ timeout=self.DEV_SERVER_TIMEOUT,
+ period=5)
except timeout_util.TimeoutError:
self.terminate()
raise DevServerStartupError('Devserver did not start')
def run(self):
"""Kicks off devserver in a separate process and waits for it to finish."""
# Truncate the log file if it already exists.
- if os.path.exists(self.log_filename):
- osutils.SafeUnlink(self.log_filename, sudo=True)
+ if os.path.exists(self.log_file):
+ osutils.SafeUnlink(self.log_file, sudo=True)
+ port = self.port if self.port else 0
cmd = [self.devserver_bin,
- '--port', str(self.port),
'--pidfile', cros_build_lib.ToChrootPath(self._pid_file),
- '--logfile', cros_build_lib.ToChrootPath(self.log_filename)]
+ '--logfile', cros_build_lib.ToChrootPath(self.log_file),
+ '--port=%d' % port]
+
+ if not self.port:
+ cmd.append('--portfile=%s' % cros_build_lib.ToChrootPath(self.port_file))
if self.static_dir:
cmd.append(
cmd.append('--board=%s' % self.board)
result = self._RunCommand(
- cmd, enter_chroot=True,
+ cmd, enter_chroot=True, chroot_args=['--no-ns-pid'],
cwd=constants.SOURCE_ROOT, error_code_ok=True,
redirect_stdout=True, combine_stdout_stderr=True)
if result.returncode != 0:
logging.debug('Stopping devserver instance with pid %s', self._pid)
if self.is_alive():
- self._RunCommand(['kill', self._pid])
+ self._RunCommand(['kill', self._pid], error_code_ok=True)
else:
logging.debug('Devserver not running!')
return
self.join(self.KILL_TIMEOUT)
if self.is_alive():
logging.warning('Devserver is unstoppable. Killing with SIGKILL')
- self._RunCommand(['kill', '-9', self._pid])
+ try:
+ self._RunCommand(['kill', '-9', self._pid])
+ except cros_build_lib.RunCommandError as e:
+ raise DevServerStopError('Unable to stop devserver: %s' % e)
def PrintLog(self):
"""Print devserver output to stdout."""
- print self.TailLog(num_lines='+1')
+ print(self.TailLog(num_lines='+1'))
def TailLog(self, num_lines=50):
"""Returns the most recent |num_lines| lines of the devserver log file."""
- fname = self.log_filename
- if os.path.exists(fname):
+ fname = self.log_file
+ # We use self._RunCommand here to check the existence of the log
+ # file, so it works for RemoteDevserverWrapper as well.
+ if self._RunCommand(
+ ['test', '-f', fname], error_code_ok=True).returncode == 0:
result = self._RunCommand(['tail', '-n', str(num_lines), fname],
capture_output=True)
output = '--- Start output from %s ---' % fname
kwargs.setdefault('debug_level', logging.DEBUG)
return self.device.RunCommand(*args, **kwargs)
+ def _ReadPortNumber(self):
+ """Read port number from file."""
+ if not self.is_alive():
+ raise DevServerStartupError('Devserver terminated unexpectedly!')
+
+ def PortFileExists():
+ result = self._RunCommand(['test', '-f', self.port_file],
+ error_code_ok=True)
+ return result.returncode == 0
+
+ try:
+ timeout_util.WaitForReturnTrue(PortFileExists,
+ timeout=self.DEV_SERVER_TIMEOUT,
+ period=5)
+ except timeout_util.TimeoutError:
+ self.terminate()
+ raise DevServerStartupError('Devserver portfile does not exist!')
+
+ self.port = int(self._RunCommand(
+ ['cat', self.port_file], capture_output=True).output.strip())
+
def IsReady(self):
"""Returns True if devserver is ready to accept requests."""
if not self.is_alive():
def run(self):
"""Launches a devserver process on the device."""
- self._RunCommand(['cat', '/dev/null', '>|', self.log_filename])
- self._RunCommand(['pkill', os.path.basename(self.devserver_bin)],
- error_code_ok=True)
+ self._RunCommand(['cat', '/dev/null', '>|', self.log_file])
+
+ port = self.port if self.port else 0
cmd = ['python', self.devserver_bin,
- '--port=%s' % str(self.port),
- '--logfile=%s' % self.log_filename,
- '--pidfile', self._pid_file]
+ '--logfile=%s' % self.log_file,
+ '--pidfile', self._pid_file,
+ '--port=%d' % port,]
+
+ if not self.port:
+ cmd.append('--portfile=%s' % self.port_file)
if self.static_dir:
cmd.append('--static_dir=%s' % self.static_dir)
- logging.info('Starting devserver %s', self.GetDevServerURL(ip=self.hostname,
- port=self.port))
+ logging.info('Starting devserver on %s', self.hostname)
result = self._RunCommand(cmd, error_code_ok=True, redirect_stdout=True,
combine_stdout_stderr=True)
if result.returncode != 0:
if 'ImportError: No module named cherrypy' in result.output:
logging.error(self.CHERRYPY_ERROR_MSG)
+ def GetURL(self, sub_dir=None):
+ """Returns the URL of this devserver instance."""
+ return self.GetDevServerURL(ip=self.hostname, port=self.port,
+ sub_dir=sub_dir)
+
@classmethod
def WipePayloadCache(cls, devserver_bin='start_devserver', static_dir=None):
"""Cleans up devserver cache of payloads."""