Allow resubmitting the same NSV for scratch module builds.

Signed-off-by: Merlin Mathesius <mmathesi@redhat.com>
This commit is contained in:
Merlin Mathesius
2019-03-27 16:58:50 -05:00
committed by mprahl
parent da57146bf2
commit 663b7fc4d0
3 changed files with 131 additions and 5 deletions

View File

@@ -353,6 +353,16 @@ class ModuleBuild(MBSBase):
return session.query(ModuleBuild).filter_by(
name=name, stream=stream, version=str(version), context=context, **kwargs).first()
@staticmethod
def get_scratch_builds_from_nsvc(session, name, stream, version, context, **kwargs):
"""
Returns all scratch builds defined by NSVC. This is done by using the supplied `context`
as a match prefix. Optional kwargs are passed to SQLAlchemy filter_by method.
"""
return session.query(ModuleBuild).filter_by(
name=name, stream=stream, version=str(version), scratch=True, **kwargs)\
.filter(ModuleBuild.context.like(context + '%'))
@staticmethod
def get_last_builds_in_stream_version_lte(session, name, stream_version):
"""
@@ -831,10 +841,10 @@ class ModuleBuild(MBSBase):
return rv
def __repr__(self):
return (("<ModuleBuild %s, id=%d, stream=%s, version=%s, state %r,"
" batch %r, state_reason %r>")
% (self.name, self.id, self.stream, self.version, INVERSE_BUILD_STATES[self.state],
self.batch, self.state_reason))
return (("<ModuleBuild %s, id=%d, stream=%s, version=%s, scratch=%r,"
" state %r, batch %r, state_reason %r>")
% (self.name, self.id, self.stream, self.version, self.scratch,
INVERSE_BUILD_STATES[self.state], self.batch, self.state_reason))
class ModuleBuildTrace(MBSBase):

View File

@@ -602,6 +602,10 @@ def submit_module_build(username, mmd, params):
"""
import koji # Placed here to avoid py2/py3 conflicts...
log.debug('Submitted %s module build for %s:%s:%s',
("scratch" if params.get('scratch', False) else "normal"),
mmd.get_name(), mmd.get_stream(), mmd.get_version())
if mmd.get_name() in conf.base_module_names:
raise ValidationError(
'You cannot build a module named "{}" since it is a base module'.format(mmd.get_name()))
@@ -639,7 +643,7 @@ def submit_module_build(username, mmd, params):
log.debug('Checking whether module build already exists: %s.', nsvc)
module = models.ModuleBuild.get_build_from_nsvc(
db.session, mmd.get_name(), mmd.get_stream(), version_str, mmd.get_context())
if module:
if module and not params.get('scratch', False):
if module.state != models.BUILD_STATES['failed']:
log.info("Skipping rebuild of %s, only rebuild of modules in failed state "
"is allowed.", nsvc)
@@ -669,6 +673,18 @@ def submit_module_build(username, mmd, params):
module.transition(conf, transition_to, "Resubmitted by %s" % username)
log.info("Resumed existing module build in previous state %s" % module.state)
else:
# make NSVC unique for every scratch build
context_suffix = ''
if params.get('scratch', False):
log.debug('Checking for existing scratch module builds by NSVC')
scrmods = models.ModuleBuild.get_scratch_builds_from_nsvc(
db.session, mmd.get_name(), mmd.get_stream(), version_str, mmd.get_context())
scrmod_contexts = [scrmod.context for scrmod in scrmods]
log.debug('Found %d previous scratch module build context(s): %s',
scrmods.count(), ",".join(scrmod_contexts))
# append incrementing counter to context
context_suffix = '.' + str(scrmods.count() + 1)
mmd.set_context(mmd.get_context() + context_suffix)
log.debug('Creating new module build')
module = models.ModuleBuild.create(
db.session,
@@ -685,6 +701,7 @@ def submit_module_build(username, mmd, params):
)
(module.ref_build_context, module.build_context, module.runtime_context,
module.context) = module.contexts_from_mmd(module.modulemd)
module.context += context_suffix
all_modules_skipped = False
db.session.add(module)

View File

@@ -1198,6 +1198,105 @@ class TestBuild:
}
assert data == expected
@patch('module_build_service.auth.get_user', return_value=user)
@patch('module_build_service.scm.SCM')
@patch('module_build_service.config.Config.modules_allow_scratch',
new_callable=PropertyMock, return_value=True)
def test_submit_scratch_vs_normal(
self, mocked_allow_scratch, mocked_scm, mocked_get_user, conf_system, dbg, hmsc):
"""
Tests that submitting a scratch build with the same NSV as a completed
normal build succeeds
"""
FakeSCM(mocked_scm, 'testmodule', 'testmodule.yaml',
'620ec77321b2ea7b0d67d82992dda3e1d67055b4')
# Post so a module is in the init phase
post_url = '/module-build-service/1/module-builds/'
post_data = {
'branch': 'master',
'scmurl': 'https://src.stg.fedoraproject.org/modules/'
'testmodule.git?#620ec77321b2ea7b0d67d82992dda3e1d67055b4',
'scratch': False,
}
rv = self.client.post(post_url, data=json.dumps(post_data))
assert rv.status_code == 201
data = json.loads(rv.data)
module_build_id = data['id']
module_build = models.ModuleBuild.query.filter_by(id=module_build_id).one()
# make sure normal build has expected context
assert module_build.context == '9c690d0e'
# Run the backend
stop = module_build_service.scheduler.make_simple_stop_condition(db.session)
module_build_service.scheduler.main([], stop)
# Post again as a scratch build and make sure it succeeds
post_data['scratch'] = True
rv2 = self.client.post(post_url, data=json.dumps(post_data))
assert rv2.status_code == 201
data = json.loads(rv2.data)
module_build_id = data['id']
module_build = models.ModuleBuild.query.filter_by(id=module_build_id).one()
# make sure scratch build has context with unique suffix
assert module_build.context == '9c690d0e.1'
@patch('module_build_service.auth.get_user', return_value=user)
@patch('module_build_service.scm.SCM')
@patch('module_build_service.config.Config.modules_allow_scratch',
new_callable=PropertyMock, return_value=True)
def test_submit_normal_vs_scratch(
self, mocked_allow_scratch, mocked_scm, mocked_get_user, conf_system, dbg, hmsc):
"""
Tests that submitting a normal build with the same NSV as a completed
scratch build succeeds
"""
FakeSCM(mocked_scm, 'testmodule', 'testmodule.yaml',
'620ec77321b2ea7b0d67d82992dda3e1d67055b4')
# Post so a scratch module build is in the init phase
post_url = '/module-build-service/1/module-builds/'
post_data = {
'branch': 'master',
'scmurl': 'https://src.stg.fedoraproject.org/modules/'
'testmodule.git?#620ec77321b2ea7b0d67d82992dda3e1d67055b4',
'scratch': True,
}
rv = self.client.post(post_url, data=json.dumps(post_data))
assert rv.status_code == 201
# Run the backend
stop = module_build_service.scheduler.make_simple_stop_condition(db.session)
module_build_service.scheduler.main([], stop)
# Post again as a non-scratch build and make sure it succeeds
post_data['scratch'] = False
rv2 = self.client.post(post_url, data=json.dumps(post_data))
assert rv2.status_code == 201
@patch('module_build_service.auth.get_user', return_value=user)
@patch('module_build_service.scm.SCM')
@patch('module_build_service.config.Config.modules_allow_scratch',
new_callable=PropertyMock, return_value=True)
def test_submit_scratch_vs_scratch(
self, mocked_allow_scratch, mocked_scm, mocked_get_user, conf_system, dbg, hmsc):
"""
Tests that submitting a scratch build with the same NSV as a completed
scratch build succeeds
"""
FakeSCM(mocked_scm, 'testmodule', 'testmodule.yaml',
'620ec77321b2ea7b0d67d82992dda3e1d67055b4')
# Post so a scratch module build is in the init phase
post_url = '/module-build-service/1/module-builds/'
post_data = {
'branch': 'master',
'scmurl': 'https://src.stg.fedoraproject.org/modules/'
'testmodule.git?#620ec77321b2ea7b0d67d82992dda3e1d67055b4',
'scratch': True,
}
rv = self.client.post(post_url, data=json.dumps(post_data))
assert rv.status_code == 201
# Run the backend
stop = module_build_service.scheduler.make_simple_stop_condition(db.session)
module_build_service.scheduler.main([], stop)
# Post scratch build again and make sure it succeeds
rv2 = self.client.post(post_url, data=json.dumps(post_data))
assert rv2.status_code == 201
@patch('module_build_service.auth.get_user', return_value=user)
@patch('module_build_service.scm.SCM')
def test_submit_build_repo_regen_not_started_batch(self, mocked_scm, mocked_get_user,