PR#1662: CGUninitBuild for cancelling CG reservations

Merges #1662
https://pagure.io/koji/pull-request/1662

Fixes: #1610
https://pagure.io/koji/issue/1610
[RFE] Cancel CG reservation
This commit is contained in:
Tomas Kopecek 2019-10-16 10:03:51 +02:00
commit 87685df50e
2 changed files with 171 additions and 0 deletions

View file

@ -5816,6 +5816,43 @@ def cg_init_build(cg, data):
return {'build_id': build_id, 'token': token}
def cg_refund_build(cg, build_id, token, state=koji.BUILD_STATES['FAILED']):
"""If build is reserved and not finished yet, there is an option
to release reservation and mark build either FAILED or CANCELED.
For this calling CG needs to know build_id and reservation token.
On success it doesn't return nothing. On error it raises an exception.
"""
if state not in (koji.BUILD_STATES['FAILED'], koji.BUILD_STATES['CANCELED']):
raise koji.GenericError("Only FAILED/CANCELLED build states are allowed")
assert_cg(cg)
binfo = get_build(build_id, strict=True)
if binfo['state'] != koji.BUILD_STATES['BUILDING']:
raise koji.GenericError('Build ID %s is not in BUILDING state' % build_id)
build_token = get_reservation_token(build_id)
if not build_token or build_token['token'] != token:
raise koji.GenericError("Token doesn't match build ID %s" % build_id)
cg_id = lookup_name('content_generator', cg, strict=True)['id']
if binfo['cg_id'] != cg_id:
raise koji.GenericError('Build ID %s is not reserved by this CG' % build_id)
koji.plugin.run_callbacks('preBuildStateChange', attribute='state',
old=koji.BUILD_STATES['BUILDING'], new=state, info=binfo)
update = UpdateProcessor('build', values={'id': build_id}, clauses=["id = %(id)s"])
update.set(state=state)
update.rawset(completion_time='NOW()')
update.execute()
binfo = get_build(build_id, strict=True)
koji.plugin.run_callbacks('postBuildStateChange', attribute='state',
old=koji.BUILD_STATES['BUILDING'], new=state, info=binfo)
def cg_import(metadata, directory, token=None):
"""Import build from a content generator
@ -9939,6 +9976,7 @@ class RootExports(object):
import_archive(fullpath, buildinfo, type, typeInfo)
CGInitBuild = staticmethod(cg_init_build)
CGRefundBuild = staticmethod(cg_refund_build)
CGImport = staticmethod(cg_import)
untaggedBuilds = staticmethod(untagged_builds)

View file

@ -11,6 +11,8 @@ import koji
import kojihub
from koji import GenericError
IP = kojihub.InsertProcessor
UP = kojihub.UpdateProcessor
class TestCGImporter(unittest.TestCase):
TMP_PATH = os.path.join(os.path.dirname(__file__), 'tmptest')
@ -224,3 +226,134 @@ class TestMatchKojiFile(unittest.TestCase):
self.get_build.return_value = self.build1
with self.assertRaises(koji.GenericError):
self.importer.match_kojifile(comp)
class TestCGReservation(unittest.TestCase):
def getInsert(self, *args, **kwargs):
insert = IP(*args, **kwargs)
insert.execute = mock.MagicMock()
self.inserts.append(insert)
return insert
def getUpdate(self, *args, **kwargs):
update = UP(*args, **kwargs)
update.execute = mock.MagicMock()
self.updates.append(update)
return update
def setUp(self):
self.InsertProcessor = mock.patch('kojihub.InsertProcessor',
side_effect=self.getInsert).start()
self.UpdateProcessor = mock.patch('kojihub.UpdateProcessor',
side_effect=self.getUpdate).start()
self.inserts = []
self.updates = []
self.context = mock.patch('kojihub.context').start()
self.context.session.user_id = 123456
self.mock_cursor = mock.MagicMock()
self.context.cnx.cursor.return_value = self.mock_cursor
def tearDown(self):
mock.patch.stopall()
@mock.patch("kojihub.new_build")
@mock.patch("kojihub.get_user")
@mock.patch("kojihub.lookup_name")
@mock.patch("kojihub.assert_cg")
def test_init_build_ok(self, assert_cg, lookup_name, get_user, new_build):
assert_cg.return_value = True
lookup_name.return_value = {'id': 21, 'name': 'cg_name'}
get_user.return_value = {'id': 123456, 'name': 'username'}
new_build.return_value = 654
cg = 'content_generator_name'
self.mock_cursor.fetchone.side_effect = [
[333], # get pkg_id
[1234], # get nextval pkg_id
]
self.mock_cursor.fetchall.side_effect = [
[[]],
]
data = {
'name': 'pkg_name',
'version': 'pkg_version',
'release': 'pkg_release',
'extra': {},
}
kojihub.cg_init_build(cg, data)
lookup_name.assert_called_once_with('content_generator', cg, strict=True)
assert_cg.assert_called_once_with(cg)
self.assertEqual(1, len(self.inserts))
insert = self.inserts[0]
self.assertEqual(insert.table, 'build_reservations')
self.assertEqual(insert.data['build_id'], 654)
self.assertTrue('token' in insert.data)
self.assertEqual(insert.rawdata, {'created': 'NOW()'})
@mock.patch("koji.plugin.run_callbacks")
@mock.patch("kojihub.get_reservation_token")
@mock.patch("kojihub.lookup_name")
@mock.patch("kojihub.get_build")
@mock.patch("kojihub.assert_cg")
def test_uninit_build_ok(self, assert_cg, get_build, lookup_name, get_reservation_token,
run_callbacks):
assert_cg.return_value = True
build_id = 1122
cg_id = 888
cg = 'content_generator_name'
get_build.side_effect = [
{
'id': build_id,
'state': koji.BUILD_STATES['BUILDING'],
'cg_id': cg_id,
},
{
'id': build_id,
'state': koji.BUILD_STATES['FAILED'],
'cg_id': cg_id,
},
]
token = 'random_token'
get_reservation_token.return_value = {'build_id': build_id, 'token': token}
lookup_name.return_value = {'name': cg, 'id': cg_id}
kojihub.cg_refund_build(cg, build_id, token)
assert_cg.assert_called_once_with(cg)
get_build.assert_has_calls([
mock.call(build_id, strict=True),
mock.call(build_id, strict=True),
])
get_reservation_token.assert_called_once_with(build_id)
lookup_name.assert_called_once_with('content_generator', cg, strict=True)
self.assertEqual(len(self.updates), 1)
update = self.updates[0]
self.assertEqual(update.table, 'build')
self.assertEqual(update.values['id'], build_id)
self.assertEqual(update.data['state'], koji.BUILD_STATES['FAILED'])
self.assertEqual(update.rawdata, {'completion_time': 'NOW()'})
run_callbacks.assert_has_calls([
mock.call('preBuildStateChange', attribute='state',
old=koji.BUILD_STATES['BUILDING'],
new=koji.BUILD_STATES['FAILED'],
info={
'state': koji.BUILD_STATES['BUILDING'],
'cg_id': cg_id,
'id': build_id}
),
mock.call('postBuildStateChange', attribute='state',
old=koji.BUILD_STATES['BUILDING'],
new=koji.BUILD_STATES['FAILED'],
info={
'state': koji.BUILD_STATES['FAILED'],
'cg_id': cg_id,
'id': build_id}
),
])