Merge pull request #2823 from jakedt/buildhilarity

Release database connections after updating build statuses.
This commit is contained in:
Jake Moshenko 2017-07-26 12:45:47 -04:00 committed by GitHub
commit c8cdd39d04
3 changed files with 43 additions and 46 deletions

View file

@ -76,7 +76,7 @@ class StatusHandler(object):
yield From(self._append_log_message(phase, self._build_logs.PHASE, extra_data)) yield From(self._append_log_message(phase, self._build_logs.PHASE, extra_data))
# Update the repository build with the new phase # Update the repository build with the new phase
raise Return(self._build_model.update_phase(self._uuid, phase)) raise Return(self._build_model.update_phase_then_close(self._uuid, phase))
def __enter__(self): def __enter__(self):
return self._status return self._status

View file

@ -6,9 +6,9 @@ from peewee import JOIN_LEFT_OUTER
import features import features
from data.database import (BuildTriggerService, RepositoryBuildTrigger, Repository, Namespace, User, from data.database import (BuildTriggerService, RepositoryBuildTrigger, Repository, Namespace, User,
RepositoryBuild, BUILD_PHASE, db_for_update, db_random_func) RepositoryBuild, BUILD_PHASE, db_random_func, UseThenDisconnect)
from data.model import (InvalidBuildTriggerException, InvalidRepositoryBuildException, from data.model import (InvalidBuildTriggerException, InvalidRepositoryBuildException,
db_transaction, user as user_model) db_transaction, user as user_model, config)
PRESUMED_DEAD_BUILD_AGE = timedelta(days=15) PRESUMED_DEAD_BUILD_AGE = timedelta(days=15)
PHASES_NOT_ALLOWED_TO_CANCEL_FROM = (BUILD_PHASE.PUSHING, BUILD_PHASE.COMPLETE, PHASES_NOT_ALLOWED_TO_CANCEL_FROM = (BUILD_PHASE.PUSHING, BUILD_PHASE.COMPLETE,
@ -153,36 +153,40 @@ def get_pull_robot_name(trigger):
return trigger.pull_robot.username return trigger.pull_robot.username
def _get_build_row_for_update(build_uuid): def _get_build_row(build_uuid):
return db_for_update(RepositoryBuild.select().where(RepositoryBuild.uuid == build_uuid)).get() return RepositoryBuild.select().where(RepositoryBuild.uuid == build_uuid).get()
def update_phase(build_uuid, phase): def update_phase_then_close(build_uuid, phase):
""" A function to change the phase of a build """ """ A function to change the phase of a build """
try: with UseThenDisconnect(config.app_config):
build = _get_build_row_for_update(build_uuid) try:
except RepositoryBuild.DoesNotExist: build = _get_build_row(build_uuid)
except RepositoryBuild.DoesNotExist:
return False return False
# Can't update a cancelled build # Can't update a cancelled build
if build.phase == BUILD_PHASE.CANCELLED: if build.phase == BUILD_PHASE.CANCELLED:
return False return False
build.phase = phase updated = (RepositoryBuild
build.save() .update(phase=phase)
return True .where(RepositoryBuild.id == build.id, RepositoryBuild.phase == build.phase)
.execute())
return updated > 0
def create_cancel_build_in_queue(build, build_queue): def create_cancel_build_in_queue(build_phase, build_queue_id, build_queue):
""" A function to cancel a build before it leaves the queue """ """ A function to cancel a build before it leaves the queue """
def cancel_build(): def cancel_build():
cancelled = False cancelled = False
if build.queue_id is not None: if build_queue_id is not None:
cancelled = build_queue.cancel(build.queue_id) cancelled = build_queue.cancel(build_queue_id)
if build.phase != BUILD_PHASE.WAITING: if build_phase != BUILD_PHASE.WAITING:
return False return False
return cancelled return cancelled
@ -190,42 +194,35 @@ def create_cancel_build_in_queue(build, build_queue):
return cancel_build return cancel_build
def create_cancel_build_in_manager(build, build_canceller): def create_cancel_build_in_manager(build_phase, build_uuid, build_canceller):
""" A function to cancel the build before it starts to push """ """ A function to cancel the build before it starts to push """
def cancel_build(): def cancel_build():
if build.phase in PHASES_NOT_ALLOWED_TO_CANCEL_FROM: if build_phase in PHASES_NOT_ALLOWED_TO_CANCEL_FROM:
return False return False
return build_canceller.try_cancel_build(build.uuid) return build_canceller.try_cancel_build(build_uuid)
return cancel_build return cancel_build
def cancel_repository_build(build, build_queue): def cancel_repository_build(build, build_queue):
""" This tries to cancel the build returns true if request is successful false if it can't be cancelled """ """ This tries to cancel the build returns true if request is successful false
with db_transaction(): if it can't be cancelled """
from app import build_canceller from app import build_canceller
from buildman.jobutil.buildjob import BuildJobNotifier from buildman.jobutil.buildjob import BuildJobNotifier
# Reload the build for update.
# We are loading the build for update so checks should be as quick as possible.
try:
build = _get_build_row_for_update(build.uuid)
except RepositoryBuild.DoesNotExist:
return False
cancel_builds = [create_cancel_build_in_queue(build, build_queue), cancel_builds = [create_cancel_build_in_queue(build.phase, build.queue_id, build_queue),
create_cancel_build_in_manager(build, build_canceller), ] create_cancel_build_in_manager(build.phase, build.uuid, build_canceller), ]
original_phase = build.phase for cancelled in cancel_builds:
for cancelled in cancel_builds: if cancelled():
if cancelled(): updated = update_phase_then_close(build.uuid, BUILD_PHASE.CANCELLED)
build.phase = BUILD_PHASE.CANCELLED if updated:
BuildJobNotifier(build.uuid).send_notification("build_cancelled") BuildJobNotifier(build.uuid).send_notification("build_cancelled")
build.save()
return True return updated
build.phase = original_phase
build.save() return False
return False
def get_archivable_build(): def get_archivable_build():

View file

@ -2,7 +2,7 @@ import unittest
from unittest import TestCase from unittest import TestCase
from app import app from app import app
from data.model.build import get_repository_build, update_phase, create_repository_build from data.model.build import get_repository_build, update_phase_then_close, create_repository_build
from initdb import setup_database_for_testing, finished_database_for_testing from initdb import setup_database_for_testing, finished_database_for_testing
from data import model from data import model
from data.database import RepositoryBuild, Image, ImageStorage, BUILD_PHASE from data.database import RepositoryBuild, Image, ImageStorage, BUILD_PHASE
@ -83,7 +83,7 @@ class TestUpdatePhase(unittest.TestCase):
repo_build = get_repository_build(build.uuid) repo_build = get_repository_build(build.uuid)
self.assertEqual(repo_build.phase, BUILD_PHASE.WAITING) self.assertEqual(repo_build.phase, BUILD_PHASE.WAITING)
self.assertTrue(update_phase(build.uuid, BUILD_PHASE.COMPLETE)) self.assertTrue(update_phase_then_close(build.uuid, BUILD_PHASE.COMPLETE))
repo_build = get_repository_build(build.uuid) repo_build = get_repository_build(build.uuid)
@ -91,7 +91,7 @@ class TestUpdatePhase(unittest.TestCase):
repo_build.delete_instance() repo_build.delete_instance()
self.assertFalse(update_phase(repo_build.uuid, BUILD_PHASE.PULLING)) self.assertFalse(update_phase_then_close(repo_build.uuid, BUILD_PHASE.PULLING))
@staticmethod @staticmethod
def create_build(repository): def create_build(repository):