2014-11-11 23:23:15 +00:00
|
|
|
import datetime
|
2014-11-18 21:34:09 +00:00
|
|
|
import time
|
2014-11-11 23:23:15 +00:00
|
|
|
import logging
|
|
|
|
import json
|
|
|
|
import trollius
|
2014-11-14 00:41:17 +00:00
|
|
|
import re
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-11-12 19:03:07 +00:00
|
|
|
from autobahn.wamp.exception import ApplicationError
|
2014-11-14 00:41:17 +00:00
|
|
|
|
2014-11-18 20:45:56 +00:00
|
|
|
from buildman.server import BuildJobResult
|
2014-11-25 21:14:44 +00:00
|
|
|
from buildman.component.basecomponent import BaseComponent
|
2014-12-31 16:33:56 +00:00
|
|
|
from buildman.jobutil.buildjob import BuildJobLoadException
|
2014-11-25 21:14:44 +00:00
|
|
|
from buildman.jobutil.buildpack import BuildPackage, BuildPackageException
|
|
|
|
from buildman.jobutil.buildstatus import StatusHandler
|
|
|
|
from buildman.jobutil.workererror import WorkerError
|
2014-11-14 00:41:17 +00:00
|
|
|
|
|
|
|
from data.database import BUILD_PHASE
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-11-14 20:31:02 +00:00
|
|
|
HEARTBEAT_DELTA = datetime.timedelta(seconds=30)
|
|
|
|
HEARTBEAT_TIMEOUT = 10
|
2014-11-26 22:02:49 +00:00
|
|
|
INITIAL_TIMEOUT = 25
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-01 17:11:23 +00:00
|
|
|
SUPPORTED_WORKER_VERSIONS = ['0.1-beta']
|
|
|
|
|
2014-11-30 22:48:02 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-11-18 20:45:56 +00:00
|
|
|
class ComponentStatus(object):
|
|
|
|
""" ComponentStatus represents the possible states of a component. """
|
2014-11-14 19:53:35 +00:00
|
|
|
JOINING = 'joining'
|
|
|
|
WAITING = 'waiting'
|
|
|
|
RUNNING = 'running'
|
|
|
|
BUILDING = 'building'
|
|
|
|
TIMED_OUT = 'timeout'
|
|
|
|
|
2014-11-11 23:23:15 +00:00
|
|
|
class BuildComponent(BaseComponent):
|
|
|
|
""" An application session component which conducts one (or more) builds. """
|
|
|
|
def __init__(self, config, realm=None, token=None, **kwargs):
|
|
|
|
self.expected_token = token
|
|
|
|
self.builder_realm = realm
|
|
|
|
|
2014-11-18 21:34:09 +00:00
|
|
|
self.parent_manager = None
|
2014-12-16 18:41:30 +00:00
|
|
|
self.registry_hostname = None
|
2014-11-18 21:34:09 +00:00
|
|
|
|
|
|
|
self._component_status = ComponentStatus.JOINING
|
|
|
|
self._last_heartbeat = None
|
|
|
|
self._current_job = None
|
|
|
|
self._build_status = None
|
|
|
|
self._image_info = None
|
|
|
|
|
2014-11-11 23:23:15 +00:00
|
|
|
BaseComponent.__init__(self, config, **kwargs)
|
|
|
|
|
2014-11-18 20:45:56 +00:00
|
|
|
def onConnect(self):
|
2014-11-11 23:23:15 +00:00
|
|
|
self.join(self.builder_realm)
|
|
|
|
|
|
|
|
def onJoin(self, details):
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.debug('Registering methods and listeners for component %s', self.builder_realm)
|
2014-12-22 17:14:16 +00:00
|
|
|
yield trollius.From(self.register(self._on_ready, u'io.quay.buildworker.ready'))
|
|
|
|
yield trollius.From(self.register(self._ping, u'io.quay.buildworker.ping'))
|
|
|
|
yield trollius.From(self.subscribe(self._on_heartbeat, 'io.quay.builder.heartbeat'))
|
|
|
|
yield trollius.From(self.subscribe(self._on_log_message, 'io.quay.builder.logmessage'))
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
yield trollius.From(self._set_status(ComponentStatus.WAITING))
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
def is_ready(self):
|
2014-11-18 20:45:56 +00:00
|
|
|
""" Determines whether a build component is ready to begin a build. """
|
|
|
|
return self._component_status == ComponentStatus.RUNNING
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@trollius.coroutine
|
2014-11-18 20:45:56 +00:00
|
|
|
def start_build(self, build_job):
|
|
|
|
""" Starts a build. """
|
2014-11-14 00:41:17 +00:00
|
|
|
self._current_job = build_job
|
2014-12-23 16:17:23 +00:00
|
|
|
self._build_status = StatusHandler(self.build_logs, build_job.repo_build.uuid)
|
2014-11-14 00:41:17 +00:00
|
|
|
self._image_info = {}
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
yield trollius.From(self._set_status(ComponentStatus.BUILDING))
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2015-02-03 18:01:42 +00:00
|
|
|
# Send the notification that the build has started.
|
|
|
|
build_job.send_notification('build_start')
|
|
|
|
|
2014-11-11 23:23:15 +00:00
|
|
|
# Retrieve the job's buildpack.
|
2014-12-16 18:41:30 +00:00
|
|
|
buildpack_url = self.user_files.get_file_url(build_job.repo_build.resource_key,
|
2014-11-12 19:03:07 +00:00
|
|
|
requires_cors=False)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2015-02-03 18:01:42 +00:00
|
|
|
logger.debug('Retrieving build package: %s', buildpack_url)
|
2014-11-11 23:23:15 +00:00
|
|
|
buildpack = None
|
|
|
|
try:
|
2014-11-12 19:03:07 +00:00
|
|
|
buildpack = BuildPackage.from_url(buildpack_url)
|
2014-11-11 23:23:15 +00:00
|
|
|
except BuildPackageException as bpe:
|
|
|
|
self._build_failure('Could not retrieve build package', bpe)
|
2014-12-31 16:33:56 +00:00
|
|
|
raise trollius.Return()
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
# Extract the base image information from the Dockerfile.
|
|
|
|
parsed_dockerfile = None
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.debug('Parsing dockerfile')
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
try:
|
|
|
|
build_config = build_job.build_config
|
|
|
|
except BuildJobLoadException as irbe:
|
|
|
|
self._build_failure('Could not load build job information', irbe)
|
|
|
|
|
2014-11-11 23:23:15 +00:00
|
|
|
try:
|
2014-11-12 19:03:07 +00:00
|
|
|
parsed_dockerfile = buildpack.parse_dockerfile(build_config.get('build_subdir'))
|
2014-11-11 23:23:15 +00:00
|
|
|
except BuildPackageException as bpe:
|
|
|
|
self._build_failure('Could not find Dockerfile in build package', bpe)
|
2014-12-31 16:33:56 +00:00
|
|
|
raise trollius.Return()
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
image_and_tag_tuple = parsed_dockerfile.get_image_and_tag()
|
|
|
|
if image_and_tag_tuple is None or image_and_tag_tuple[0] is None:
|
2014-11-14 19:53:35 +00:00
|
|
|
self._build_failure('Missing FROM line in Dockerfile')
|
2014-12-31 16:33:56 +00:00
|
|
|
raise trollius.Return()
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
base_image_information = {
|
2014-11-18 20:45:56 +00:00
|
|
|
'repository': image_and_tag_tuple[0],
|
|
|
|
'tag': image_and_tag_tuple[1]
|
2014-11-11 23:23:15 +00:00
|
|
|
}
|
|
|
|
|
2014-11-14 00:41:17 +00:00
|
|
|
# Extract the number of steps from the Dockerfile.
|
|
|
|
with self._build_status as status_dict:
|
|
|
|
status_dict['total_commands'] = len(parsed_dockerfile.commands)
|
|
|
|
|
2014-11-11 23:23:15 +00:00
|
|
|
# Add the pull robot information, if any.
|
2015-01-29 23:01:42 +00:00
|
|
|
if build_job.pull_credentials:
|
|
|
|
base_image_information['username'] = build_job.pull_credentials.get('username', '')
|
|
|
|
base_image_information['password'] = build_job.pull_credentials.get('password', '')
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-11-20 19:36:22 +00:00
|
|
|
# Retrieve the repository's fully qualified name.
|
2014-12-16 18:41:30 +00:00
|
|
|
repo = build_job.repo_build.repository
|
2014-11-11 23:23:15 +00:00
|
|
|
repository_name = repo.namespace_user.username + '/' + repo.name
|
|
|
|
|
|
|
|
# Parse the build queue item into build arguments.
|
2014-11-18 20:45:56 +00:00
|
|
|
# build_package: URL to the build package to download and untar/unzip.
|
|
|
|
# sub_directory: The location within the build package of the Dockerfile and the build context.
|
|
|
|
# repository: The repository for which this build is occurring.
|
|
|
|
# registry: The registry for which this build is occuring (e.g. 'quay.io', 'staging.quay.io').
|
|
|
|
# pull_token: The token to use when pulling the cache for building.
|
|
|
|
# push_token: The token to use to push the built image.
|
|
|
|
# tag_names: The name(s) of the tag(s) for the newly built image.
|
|
|
|
# base_image: The image name and credentials to use to conduct the base image pull.
|
|
|
|
# repository: The repository to pull.
|
|
|
|
# tag: The tag to pull.
|
|
|
|
# username: The username for pulling the base image (if any).
|
|
|
|
# password: The password for pulling the base image (if any).
|
2014-11-11 23:23:15 +00:00
|
|
|
build_arguments = {
|
2014-11-18 20:45:56 +00:00
|
|
|
'build_package': buildpack_url,
|
|
|
|
'sub_directory': build_config.get('build_subdir', ''),
|
|
|
|
'repository': repository_name,
|
2014-12-16 18:41:30 +00:00
|
|
|
'registry': self.registry_hostname,
|
|
|
|
'pull_token': build_job.repo_build.access_token.code,
|
|
|
|
'push_token': build_job.repo_build.access_token.code,
|
2014-11-18 20:45:56 +00:00
|
|
|
'tag_names': build_config.get('docker_tags', ['latest']),
|
|
|
|
'base_image': base_image_information,
|
|
|
|
'cached_tag': build_job.determine_cached_tag() or ''
|
2014-11-11 23:23:15 +00:00
|
|
|
}
|
|
|
|
|
2014-11-12 19:03:07 +00:00
|
|
|
# Invoke the build.
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.debug('Invoking build: %s', self.builder_realm)
|
|
|
|
logger.debug('With Arguments: %s', build_arguments)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
self.call("io.quay.builder.build", **build_arguments).add_done_callback(self._build_complete)
|
2014-11-14 00:41:17 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2014-11-18 20:45:56 +00:00
|
|
|
def _total_completion(statuses, total_images):
|
|
|
|
""" Returns the current amount completion relative to the total completion of a build. """
|
2014-11-14 19:53:35 +00:00
|
|
|
percentage_with_sizes = float(len(statuses.values())) / total_images
|
2014-11-14 00:41:17 +00:00
|
|
|
sent_bytes = sum([status['current'] for status in statuses.values()])
|
|
|
|
total_bytes = sum([status['total'] for status in statuses.values()])
|
2014-11-14 19:53:35 +00:00
|
|
|
return float(sent_bytes) / total_bytes * percentage_with_sizes
|
2014-11-14 00:41:17 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2014-11-18 20:45:56 +00:00
|
|
|
def _process_pushpull_status(status_dict, current_phase, docker_data, images):
|
2014-11-20 19:36:22 +00:00
|
|
|
""" Processes the status of a push or pull by updating the provided status_dict and images. """
|
2014-11-14 00:41:17 +00:00
|
|
|
if not docker_data:
|
|
|
|
return
|
|
|
|
|
|
|
|
num_images = 0
|
|
|
|
status_completion_key = ''
|
|
|
|
|
|
|
|
if current_phase == 'pushing':
|
|
|
|
status_completion_key = 'push_completion'
|
|
|
|
num_images = status_dict['total_commands']
|
|
|
|
elif current_phase == 'pulling':
|
|
|
|
status_completion_key = 'pull_completion'
|
|
|
|
elif current_phase == 'priming-cache':
|
|
|
|
status_completion_key = 'cache_completion'
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
|
|
|
|
if 'progressDetail' in docker_data and 'id' in docker_data:
|
|
|
|
image_id = docker_data['id']
|
|
|
|
detail = docker_data['progressDetail']
|
|
|
|
|
|
|
|
if 'current' in detail and 'total' in detail:
|
|
|
|
images[image_id] = detail
|
|
|
|
status_dict[status_completion_key] = \
|
2014-11-18 20:45:56 +00:00
|
|
|
BuildComponent._total_completion(images, max(len(images), num_images))
|
2014-11-14 00:41:17 +00:00
|
|
|
|
2014-11-14 19:53:35 +00:00
|
|
|
def _on_log_message(self, phase, json_data):
|
2014-11-20 19:36:22 +00:00
|
|
|
""" Tails log messages and updates the build status. """
|
2014-11-14 00:41:17 +00:00
|
|
|
# Parse any of the JSON data logged.
|
|
|
|
docker_data = {}
|
|
|
|
if json_data:
|
|
|
|
try:
|
|
|
|
docker_data = json.loads(json_data)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
2015-02-03 17:48:41 +00:00
|
|
|
logger.debug('Got log message: %s: (%s) => (%s)', phase, json_data, docker_data)
|
|
|
|
|
2014-11-14 19:53:35 +00:00
|
|
|
# Extract the current status message (if any).
|
2014-11-14 00:41:17 +00:00
|
|
|
fully_unwrapped = ''
|
|
|
|
keys_to_extract = ['error', 'status', 'stream']
|
|
|
|
for key in keys_to_extract:
|
|
|
|
if key in docker_data:
|
|
|
|
fully_unwrapped = docker_data[key]
|
|
|
|
break
|
|
|
|
|
|
|
|
# Determine if this is a step string.
|
|
|
|
current_step = None
|
|
|
|
current_status_string = str(fully_unwrapped.encode('utf-8'))
|
|
|
|
|
2014-11-14 19:53:35 +00:00
|
|
|
if current_status_string and phase == BUILD_PHASE.BUILDING:
|
2014-11-14 00:41:17 +00:00
|
|
|
step_increment = re.search(r'Step ([0-9]+) :', current_status_string)
|
|
|
|
if step_increment:
|
|
|
|
current_step = int(step_increment.group(1))
|
|
|
|
|
|
|
|
# Parse and update the phase and the status_dict. The status dictionary contains
|
2014-11-14 19:53:35 +00:00
|
|
|
# the pull/push progress, as well as the current step index.
|
2014-11-14 00:41:17 +00:00
|
|
|
with self._build_status as status_dict:
|
2014-11-18 21:34:09 +00:00
|
|
|
if self._build_status.set_phase(phase):
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.debug('Build %s has entered a new phase: %s', self.builder_realm, phase)
|
2014-11-18 21:34:09 +00:00
|
|
|
|
2014-11-18 20:45:56 +00:00
|
|
|
BuildComponent._process_pushpull_status(status_dict, phase, docker_data, self._image_info)
|
2014-11-14 00:41:17 +00:00
|
|
|
|
2014-11-14 19:53:35 +00:00
|
|
|
# If the current message represents the beginning of a new step, then update the
|
|
|
|
# current command index.
|
|
|
|
if current_step is not None:
|
2014-11-14 00:41:17 +00:00
|
|
|
status_dict['current_command'] = current_step
|
|
|
|
|
|
|
|
# If the json data contains an error, then something went wrong with a push or pull.
|
|
|
|
if 'error' in docker_data:
|
|
|
|
self._build_status.set_error(docker_data['error'])
|
|
|
|
|
2014-11-14 19:53:35 +00:00
|
|
|
if current_step is not None:
|
|
|
|
self._build_status.set_command(current_status_string)
|
|
|
|
elif phase == BUILD_PHASE.BUILDING:
|
|
|
|
self._build_status.append_log(current_status_string)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _build_failure(self, error_message, exception=None):
|
2014-11-18 20:45:56 +00:00
|
|
|
""" Handles and logs a failed build. """
|
2014-11-14 00:41:17 +00:00
|
|
|
self._build_status.set_error(error_message, {
|
2015-01-29 23:01:42 +00:00
|
|
|
'internal_error': str(exception) if exception else None
|
2014-11-14 00:41:17 +00:00
|
|
|
})
|
|
|
|
|
2014-12-16 18:41:30 +00:00
|
|
|
build_id = self._current_job.repo_build.uuid
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.warning('Build %s failed with message: %s', build_id, error_message)
|
2014-11-14 00:41:17 +00:00
|
|
|
|
|
|
|
# Mark that the build has finished (in an error state)
|
2014-12-23 21:04:10 +00:00
|
|
|
trollius.async(self._build_finished(BuildJobResult.ERROR))
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
def _build_complete(self, result):
|
2014-11-18 20:45:56 +00:00
|
|
|
""" Wraps up a completed build. Handles any errors and calls self._build_finished. """
|
2014-11-11 23:23:15 +00:00
|
|
|
try:
|
2014-11-14 00:41:17 +00:00
|
|
|
# Retrieve the result. This will raise an ApplicationError on any error that occurred.
|
|
|
|
result.result()
|
|
|
|
self._build_status.set_phase(BUILD_PHASE.COMPLETE)
|
2014-12-23 21:04:10 +00:00
|
|
|
trollius.async(self._build_finished(BuildJobResult.COMPLETE))
|
2015-02-03 18:01:42 +00:00
|
|
|
|
|
|
|
# Send the notification that the build has completed successfully.
|
|
|
|
build_job.send_notification('build_success')
|
2014-11-18 20:45:56 +00:00
|
|
|
except ApplicationError as aex:
|
|
|
|
worker_error = WorkerError(aex.error, aex.kwargs.get('base_error'))
|
2014-11-14 00:41:17 +00:00
|
|
|
|
|
|
|
# Write the error to the log.
|
2014-11-21 19:27:06 +00:00
|
|
|
self._build_status.set_error(worker_error.public_message(), worker_error.extra_data(),
|
|
|
|
internal_error=worker_error.is_internal_error())
|
2014-11-14 00:41:17 +00:00
|
|
|
|
2015-02-03 18:01:42 +00:00
|
|
|
# Send the notification that the build has failed.
|
|
|
|
build_job.send_notification('build_failure', error_message=worker_error.public_message())
|
|
|
|
|
2014-11-14 00:41:17 +00:00
|
|
|
# Mark the build as completed.
|
|
|
|
if worker_error.is_internal_error():
|
2014-12-23 21:04:10 +00:00
|
|
|
trollius.async(self._build_finished(BuildJobResult.INCOMPLETE))
|
2014-11-14 00:41:17 +00:00
|
|
|
else:
|
2014-12-23 21:04:10 +00:00
|
|
|
trollius.async(self._build_finished(BuildJobResult.ERROR))
|
2014-11-14 00:41:17 +00:00
|
|
|
|
2014-12-22 17:14:16 +00:00
|
|
|
@trollius.coroutine
|
2014-11-14 00:41:17 +00:00
|
|
|
def _build_finished(self, job_status):
|
2014-11-18 20:45:56 +00:00
|
|
|
""" Alerts the parent that a build has completed and sets the status back to running. """
|
2014-12-22 17:14:16 +00:00
|
|
|
yield trollius.From(self.parent_manager.job_completed(self._current_job, job_status, self))
|
2014-11-14 00:41:17 +00:00
|
|
|
self._current_job = None
|
|
|
|
|
|
|
|
# Set the component back to a running state.
|
2014-12-31 16:33:56 +00:00
|
|
|
yield trollius.From(self._set_status(ComponentStatus.RUNNING))
|
2014-11-14 19:53:35 +00:00
|
|
|
|
2014-11-20 21:06:23 +00:00
|
|
|
@staticmethod
|
2014-11-18 20:45:56 +00:00
|
|
|
def _ping():
|
|
|
|
""" Ping pong. """
|
2014-11-14 19:53:35 +00:00
|
|
|
return 'pong'
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@trollius.coroutine
|
2014-12-01 17:11:23 +00:00
|
|
|
def _on_ready(self, token, version):
|
|
|
|
if not version in SUPPORTED_WORKER_VERSIONS:
|
2014-12-31 16:33:56 +00:00
|
|
|
logger.warning('Build component (token "%s") is running an out-of-date version: %s', token,
|
|
|
|
version)
|
|
|
|
raise trollius.Return(False)
|
2014-12-01 17:11:23 +00:00
|
|
|
|
2015-02-02 17:24:32 +00:00
|
|
|
if self._component_status != ComponentStatus.WAITING:
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.warning('Build component (token "%s") is already connected', self.expected_token)
|
2014-12-31 16:33:56 +00:00
|
|
|
raise trollius.Return(False)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
if token != self.expected_token:
|
2014-12-31 16:33:56 +00:00
|
|
|
logger.warning('Builder token mismatch. Expected: "%s". Found: "%s"', self.expected_token,
|
|
|
|
token)
|
|
|
|
raise trollius.Return(False)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
yield trollius.From(self._set_status(ComponentStatus.RUNNING))
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-11-14 20:31:02 +00:00
|
|
|
# Start the heartbeat check and updating loop.
|
2014-11-11 23:23:15 +00:00
|
|
|
loop = trollius.get_event_loop()
|
2014-11-18 20:45:56 +00:00
|
|
|
loop.create_task(self._heartbeat())
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.debug('Build worker %s is connected and ready', self.builder_realm)
|
2014-12-31 16:33:56 +00:00
|
|
|
raise trollius.Return(True)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@trollius.coroutine
|
2014-11-14 00:41:17 +00:00
|
|
|
def _set_status(self, phase):
|
2014-12-16 18:41:30 +00:00
|
|
|
if phase == ComponentStatus.RUNNING:
|
2015-01-05 18:08:25 +00:00
|
|
|
yield trollius.From(self.parent_manager.build_component_ready(self))
|
2014-12-16 18:41:30 +00:00
|
|
|
|
2014-11-14 00:41:17 +00:00
|
|
|
self._component_status = phase
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
def _on_heartbeat(self):
|
2014-11-18 20:45:56 +00:00
|
|
|
""" Updates the last known heartbeat. """
|
2014-12-22 17:14:16 +00:00
|
|
|
self._last_heartbeat = datetime.datetime.utcnow()
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
@trollius.coroutine
|
2014-11-18 20:45:56 +00:00
|
|
|
def _heartbeat(self):
|
2014-11-14 20:31:02 +00:00
|
|
|
""" Coroutine that runs every HEARTBEAT_TIMEOUT seconds, both checking the worker's heartbeat
|
|
|
|
and updating the heartbeat in the build status dictionary (if applicable). This allows
|
|
|
|
the build system to catch crashes from either end.
|
|
|
|
"""
|
2014-12-22 17:14:16 +00:00
|
|
|
yield trollius.From(trollius.sleep(INITIAL_TIMEOUT))
|
2014-11-26 22:02:49 +00:00
|
|
|
|
2014-11-11 23:23:15 +00:00
|
|
|
while True:
|
2014-11-14 20:31:02 +00:00
|
|
|
# If the component is no longer running or actively building, nothing more to do.
|
2014-11-18 20:45:56 +00:00
|
|
|
if (self._component_status != ComponentStatus.RUNNING and
|
|
|
|
self._component_status != ComponentStatus.BUILDING):
|
2015-01-29 23:21:32 +00:00
|
|
|
raise trollius.Return()
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-11-14 20:31:02 +00:00
|
|
|
# If there is an active build, write the heartbeat to its status.
|
|
|
|
build_status = self._build_status
|
|
|
|
if build_status is not None:
|
|
|
|
with build_status as status_dict:
|
2014-11-18 21:34:09 +00:00
|
|
|
status_dict['heartbeat'] = int(time.time())
|
2014-11-14 20:31:02 +00:00
|
|
|
|
2014-11-21 19:27:06 +00:00
|
|
|
# Mark the build item.
|
|
|
|
current_job = self._current_job
|
|
|
|
if current_job is not None:
|
2014-12-22 22:24:44 +00:00
|
|
|
yield trollius.From(self.parent_manager.job_heartbeat(current_job))
|
2014-11-21 19:27:06 +00:00
|
|
|
|
2014-11-14 20:31:02 +00:00
|
|
|
# Check the heartbeat from the worker.
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.debug('Checking heartbeat on realm %s', self.builder_realm)
|
2014-12-22 17:14:16 +00:00
|
|
|
if (self._last_heartbeat and
|
|
|
|
self._last_heartbeat < datetime.datetime.utcnow() - HEARTBEAT_DELTA):
|
2014-12-31 16:33:56 +00:00
|
|
|
yield trollius.From(self._timeout())
|
2015-01-29 23:21:32 +00:00
|
|
|
raise trollius.Return()
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-22 17:14:16 +00:00
|
|
|
yield trollius.From(trollius.sleep(HEARTBEAT_TIMEOUT))
|
2014-11-11 23:23:15 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@trollius.coroutine
|
2014-11-11 23:23:15 +00:00
|
|
|
def _timeout(self):
|
2015-01-29 23:21:32 +00:00
|
|
|
if self._component_status == ComponentStatus.TIMED_OUT:
|
|
|
|
raise trollius.Return()
|
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
yield trollius.From(self._set_status(ComponentStatus.TIMED_OUT))
|
2014-11-30 22:48:02 +00:00
|
|
|
logger.warning('Build component with realm %s has timed out', self.builder_realm)
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
# If we still have a running job, then it has not completed and we need to tell the parent
|
|
|
|
# manager.
|
2014-11-14 00:41:17 +00:00
|
|
|
if self._current_job is not None:
|
2015-01-29 23:01:42 +00:00
|
|
|
self._build_status.set_error('Build worker timed out', internal_error=True)
|
2014-11-14 00:41:17 +00:00
|
|
|
|
2014-11-18 20:45:56 +00:00
|
|
|
self.parent_manager.job_completed(self._current_job, BuildJobResult.INCOMPLETE, self)
|
2014-11-14 00:41:17 +00:00
|
|
|
self._build_status = None
|
|
|
|
self._current_job = None
|
2014-11-11 23:23:15 +00:00
|
|
|
|
|
|
|
# Unregister the current component so that it cannot be invoked again.
|
2015-01-29 23:13:31 +00:00
|
|
|
self.parent_manager.build_component_disposed(self, True)
|