Fix the bug when module build freezes when all the components in batch have been already built by builder.

This commit is contained in:
Jan Kaluza
2016-12-12 21:02:57 +01:00
parent 445512ea89
commit d06c13d973
7 changed files with 110 additions and 8 deletions

View File

@@ -40,6 +40,7 @@ from module_build_service.utils import (
insert_fake_baseruntime,
)
from module_build_service.messaging import RidaModule
import module_build_service.messaging
manager = Manager(app)
@@ -292,6 +293,9 @@ def runssl(host=conf.host, port=conf.port, debug=conf.debug):
""" Runs the Flask app with the HTTPS settings configured in config.py
"""
logging.info('Starting Module Build Service frontend')
module_build_service.messaging.init(conf)
ssl_ctx = _establish_ssl_context()
app.run(
host=host,

View File

@@ -228,6 +228,17 @@ class RidaModule(BaseMessage):
self.module_build_id = module_build_id
self.module_build_state = module_build_state
def init(conf, **kwargs):
"""
Initialize the messaging backend.
:param conf: a Config object from the class in config.py
:param kwargs: any additional arguments to pass to the backend handler
"""
try:
handler = _messaging_backends[conf.messaging]['init']
except KeyError:
raise KeyError("No messaging backend found for %r" % conf.messaging)
return handler(conf, **kwargs)
def publish(topic, msg, conf, service):
"""
@@ -336,6 +347,15 @@ _in_memory_work_queue = queue.Queue()
# Message id for "in_memory" messaging.
_in_memory_msg_id = 0
def _in_memory_init(conf, **kwargs):
"""
Initializes the In Memory messaging backend.
"""
global _in_memory_work_queue
global _in_memory_msg_id
_in_memory_msg_id = 0
_in_memory_work_queue = queue.Queue()
def _in_memory_publish(topic, msg, conf, service):
"""
Puts the message to _in_memory_work_queue".
@@ -364,16 +384,25 @@ def _in_memory_listen(conf, **kwargs):
while True:
yield _in_memory_work_queue.get(True)
def _no_op(conf, **kwargs):
"""
No operation.
"""
pass
_messaging_backends = {
'fedmsg': {
'init': _no_op,
'publish': _fedmsg_publish,
'listen': _fedmsg_listen,
},
'amq': {
'init': _no_op,
'publish': _amq_publish,
'listen': _amq_listen,
},
'in_memory': {
'init': _in_memory_init,
'publish': _in_memory_publish,
'listen': _in_memory_listen,
},

View File

@@ -329,9 +329,9 @@ class ModuleBuild(RidaBase):
return result
def __repr__(self):
return "<ModuleBuild %s, stream=%s, version=%s, state %r, batch %r>" % (
return "<ModuleBuild %s, stream=%s, version=%s, state %r, batch %r, state_reason %r>" % (
self.name, self.stream, self.version,
INVERSE_BUILD_STATES[self.state], self.batch)
INVERSE_BUILD_STATES[self.state], self.batch, self.state_reason)
class ComponentBuild(RidaBase):

View File

@@ -118,6 +118,7 @@ def done(config, session, msg):
and c.state != koji.BUILD_STATES["FAILED"])
]
further_work = []
if unbuilt_components:
# Increment the build batch when no components are being built and all
# have at least attempted a build (even failures) in the current batch
@@ -128,15 +129,18 @@ def done(config, session, msg):
if not unbuilt_components_in_batch:
module_build.batch += 1
module_build_service.utils.start_build_batch(
further_work += module_build_service.utils.start_build_batch(
config, module_build, session, builder)
# We don't have copr implementation finished yet, Let's fake the repo change event,
# as if copr builds finished successfully
if config.system == "copr":
return [module_build_service.messaging.KojiRepoChange('fake msg', module_build.koji_tag)]
further_work += [module_build_service.messaging.KojiRepoChange('fake msg', module_build.koji_tag)]
return further_work
else:
module_build.transition(config, state=models.BUILD_STATES['done'])
session.commit()
builder.finalize()
return further_work

View File

@@ -300,8 +300,11 @@ class Poller(threading.Thread):
# If there are no components in the build state on the module build,
# then no possible event will start off new component builds
if not module_build.current_batch(koji.BUILD_STATES['BUILDING']):
module_build_service.utils.start_build_batch(
further_work = module_build_service.utils.start_build_batch(
config, module_build, session, config.system)
for event in further_work:
log.info(" Scheduling faked event %r" % event)
self.outgoing_work_queue.put(event)
_work_queue = queue.Queue()
@@ -326,6 +329,8 @@ def graceful_stop():
def main(initial_msgs=[], return_after_build=False):
log.info("Starting module_build_service_daemon.")
module_build_service.messaging.init(conf)
for msg in initial_msgs:
outgoing_work_queue_put(msg)

View File

@@ -39,6 +39,7 @@ from module_build_service import log, models
from module_build_service.errors import ValidationError, UnprocessableEntity
from module_build_service import conf, db
from module_build_service.errors import (Unauthorized, Conflict)
import module_build_service.messaging
from multiprocessing.dummy import Pool as ThreadPool
@@ -84,7 +85,12 @@ def at_concurrent_component_threshold(config, session):
def start_build_batch(config, module, session, builder, components=None):
""" Starts a round of the build cycle for a module. """
"""
Starts a round of the build cycle for a module.
Returns list of BaseMessage instances which should be scheduled by the
scheduler.
"""
import koji # Placed here to avoid py2/py3 conflicts...
if any([c.state == koji.BUILD_STATES['BUILDING']
@@ -123,8 +129,20 @@ def start_build_batch(config, module, session, builder, components=None):
c.state_reason = "Failed to submit artifact %s to Koji" % (c.package)
continue
session.commit()
further_work = []
# If all components in this batch are already done, it can mean that they
# have been built in the past and have been skipped in this module build.
# We therefore have to generate fake KojiRepoChange message, because the
# repo has been also done in the past and build system will not send us
# any message now.
if (all(c.state == koji.BUILD_STATES['COMPLETE'] for c in unbuilt_components)
and builder.module_build_tag):
further_work += [module_build_service.messaging.KojiRepoChange(
'start_build_batch: fake msg', builder.module_build_tag['name'])]
session.commit()
return further_work
def pagination_metadata(p_query):
"""

View File

@@ -80,6 +80,7 @@ class TestModuleBuilder(GenericBuilder):
_build_id = 1
BUILD_STATE = "COMPLETE"
INSTANT_COMPLETE = False
on_build_cb = None
on_cancel_cb = None
@@ -92,6 +93,7 @@ class TestModuleBuilder(GenericBuilder):
@classmethod
def reset(cls):
TestModuleBuilder.BUILD_STATE = "COMPLETE"
TestModuleBuilder.INSTANT_COMPLETE = False
TestModuleBuilder.on_build_cb = None
TestModuleBuilder.on_cancel_cb = None
@@ -116,6 +118,10 @@ class TestModuleBuilder(GenericBuilder):
def buildroot_add_repos(self, dependencies):
pass
@property
def module_build_tag(self):
return {"name": self.tag_name + "-build"}
def _send_repo_done(self):
msg = module_build_service.messaging.KojiRepoChange(
msg_id='a faked internal message',
@@ -152,7 +158,11 @@ class TestModuleBuilder(GenericBuilder):
if TestModuleBuilder.on_build_cb:
TestModuleBuilder.on_build_cb(self, artifact_name, source)
state = koji.BUILD_STATES['BUILDING']
if TestModuleBuilder.INSTANT_COMPLETE:
state = koji.BUILD_STATES['COMPLETE']
else:
state = koji.BUILD_STATES['BUILDING']
reason = "Submitted %s to Koji" % (artifact_name)
return TestModuleBuilder._build_id, state, reason, None
@@ -262,3 +272,35 @@ class TestBuild(unittest.TestCase):
# Check that cancel_build has been called for this build
if build.task_id:
self.assertTrue(build.task_id in cancelled_tasks)
@timed(30)
@patch('module_build_service.auth.get_username', return_value='Homer J. Simpson')
@patch('module_build_service.auth.assert_is_packager')
@patch('module_build_service.scm.SCM')
def test_submit_build_instant_complete(self, mocked_scm, mocked_assert_is_packager,
mocked_get_username):
"""
Tests the build of testmodule.yaml using TestModuleBuilder which
succeeds everytime.
"""
mocked_scm_obj = MockedSCM(mocked_scm, "testmodule", "testmodule.yaml")
rv = self.client.post('/module-build-service/1/module-builds/', data=json.dumps(
{'scmurl': 'git://pkgs.stg.fedoraproject.org/modules/'
'testmodule.git?#68932c90de214d9d13feefbd35246a81b6cb8d49'}))
data = json.loads(rv.data)
module_build_id = data['id']
TestModuleBuilder.BUILD_STATE = "BUILDING"
TestModuleBuilder.INSTANT_COMPLETE = True
msgs = []
msgs.append(RidaModule("fake msg", 1, 1))
module_build_service.scheduler.main.main(msgs, True)
# All components should be built and module itself should be in "done"
# or "ready" state.
for build in models.ComponentBuild.query.filter_by(module_id=module_build_id).all():
self.assertEqual(build.state, koji.BUILD_STATES['COMPLETE'])
self.assertTrue(build.module_build.state in [models.BUILD_STATES["done"], models.BUILD_STATES["ready"]] )