Store which deliverables failed
When compose is finished successfully, and there are some failed deliverables, modify the final status to FINISHED_INCOMPLETE and log what failed for which variants/arches. This means the failures are logged twice, first time immediately after it failed, second time in the summary at the end. Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
parent
7ec409a236
commit
5f0675dd66
@ -133,6 +133,11 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
self.im.compose.respin = self.compose_respin
|
self.im.compose.respin = self.compose_respin
|
||||||
self.im.metadata_path = self.paths.compose.metadata()
|
self.im.metadata_path = self.paths.compose.metadata()
|
||||||
|
|
||||||
|
# Stores list of deliverables that failed, but did not abort the
|
||||||
|
# compose.
|
||||||
|
# {Variant.uid: {Arch: [deliverable]}}
|
||||||
|
self.failed_deliverables = {}
|
||||||
|
|
||||||
get_compose_dir = staticmethod(get_compose_dir)
|
get_compose_dir = staticmethod(get_compose_dir)
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
@ -230,6 +235,20 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
result.add(arch)
|
result.add(arch)
|
||||||
return sorted(result)
|
return sorted(result)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_file(self):
|
||||||
|
"""Path to file where the compose status will be stored."""
|
||||||
|
if not hasattr(self, '_status_file'):
|
||||||
|
self._status_file = os.path.join(self.topdir, 'STATUS')
|
||||||
|
return self._status_file
|
||||||
|
|
||||||
|
def _log_failed_deliverables(self):
|
||||||
|
for variant, variant_data in self.failed_deliverables.iteritems():
|
||||||
|
for arch, deliverables in variant_data.iteritems():
|
||||||
|
for deliverable in deliverables:
|
||||||
|
self.log_info('Failed %s on variant <%s>, arch <%s>.'
|
||||||
|
% (deliverable, variant, arch))
|
||||||
|
|
||||||
def write_status(self, stat_msg):
|
def write_status(self, stat_msg):
|
||||||
if stat_msg not in ("STARTED", "FINISHED", "DOOMED"):
|
if stat_msg not in ("STARTED", "FINISHED", "DOOMED"):
|
||||||
self.log_warning("Writing nonstandard compose status: %s" % stat_msg)
|
self.log_warning("Writing nonstandard compose status: %s" % stat_msg)
|
||||||
@ -240,14 +259,21 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
msg = "Could not modify a FINISHED compose: %s" % self.topdir
|
msg = "Could not modify a FINISHED compose: %s" % self.topdir
|
||||||
self.log_error(msg)
|
self.log_error(msg)
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
open(os.path.join(self.topdir, "STATUS"), "w").write(stat_msg + "\n")
|
|
||||||
self.notifier.send('status-change', status=stat_msg)
|
if stat_msg == 'FINISHED' and self.failed_deliverables:
|
||||||
|
stat_msg = 'FINISHED_INCOMPLETE'
|
||||||
|
self._log_failed_deliverables()
|
||||||
|
|
||||||
|
with open(self.status_file, "w") as f:
|
||||||
|
f.write(stat_msg + "\n")
|
||||||
|
|
||||||
|
if self.notifier:
|
||||||
|
self.notifier.send('status-change', status=stat_msg)
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
path = os.path.join(self.topdir, "STATUS")
|
if not os.path.isfile(self.status_file):
|
||||||
if not os.path.isfile(path):
|
|
||||||
return
|
return
|
||||||
return open(path, "r").read().strip()
|
return open(self.status_file, "r").read().strip()
|
||||||
|
|
||||||
def get_format_substs(self, **kwargs):
|
def get_format_substs(self, **kwargs):
|
||||||
"""Return a dict of basic format substitutions.
|
"""Return a dict of basic format substitutions.
|
||||||
@ -307,4 +333,9 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
Variant can be None.
|
Variant can be None.
|
||||||
"""
|
"""
|
||||||
failable = get_arch_variant_data(self.conf, 'failable_deliverables', arch, variant)
|
failable = get_arch_variant_data(self.conf, 'failable_deliverables', arch, variant)
|
||||||
return deliverable in failable
|
if deliverable in failable:
|
||||||
|
# Store failed deliverable for later logging.
|
||||||
|
variant_uid = variant.uid if variant else ''
|
||||||
|
self.failed_deliverables.setdefault(variant_uid, {}).setdefault(arch, []).append(deliverable)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@ -72,5 +72,79 @@ class ComposeTestCase(unittest.TestCase):
|
|||||||
'.n', 'Server', '3.0']))
|
'.n', 'Server', '3.0']))
|
||||||
|
|
||||||
|
|
||||||
|
class StatusTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tmp_dir = tempfile.mkdtemp()
|
||||||
|
self.logger = mock.Mock()
|
||||||
|
with mock.patch('pungi.compose.ComposeInfo'):
|
||||||
|
self.compose = Compose({}, self.tmp_dir, logger=self.logger)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.tmp_dir)
|
||||||
|
|
||||||
|
def test_get_status_non_existing(self):
|
||||||
|
status = self.compose.get_status()
|
||||||
|
self.assertIsNone(status)
|
||||||
|
|
||||||
|
def test_get_status_existing(self):
|
||||||
|
with open(os.path.join(self.tmp_dir, 'STATUS'), 'w') as f:
|
||||||
|
f.write('FOOBAR')
|
||||||
|
|
||||||
|
self.assertEqual(self.compose.get_status(), 'FOOBAR')
|
||||||
|
|
||||||
|
def test_get_status_is_dir(self):
|
||||||
|
os.mkdir(os.path.join(self.tmp_dir, 'STATUS'))
|
||||||
|
|
||||||
|
self.assertIsNone(self.compose.get_status())
|
||||||
|
|
||||||
|
def test_write_status(self):
|
||||||
|
self.compose.write_status('DOOMED')
|
||||||
|
|
||||||
|
with open(os.path.join(self.tmp_dir, 'STATUS'), 'r') as f:
|
||||||
|
self.assertEqual(f.read(), 'DOOMED\n')
|
||||||
|
|
||||||
|
def test_write_non_standard_status(self):
|
||||||
|
self.compose.write_status('FOOBAR')
|
||||||
|
|
||||||
|
self.assertEqual(self.logger.log.call_count, 1)
|
||||||
|
with open(os.path.join(self.tmp_dir, 'STATUS'), 'r') as f:
|
||||||
|
self.assertEqual(f.read(), 'FOOBAR\n')
|
||||||
|
|
||||||
|
def test_write_status_on_finished(self):
|
||||||
|
self.compose.write_status('FINISHED')
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self.compose.write_status('NOT REALLY')
|
||||||
|
|
||||||
|
def test_write_status_with_failed_deliverables(self):
|
||||||
|
self.compose.conf = {
|
||||||
|
'failable_deliverables': [
|
||||||
|
('^.+$', {
|
||||||
|
'*': ['live', 'build-image'],
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
variant = mock.Mock(uid='Server')
|
||||||
|
self.compose.can_fail(variant, 'x86_64', 'live')
|
||||||
|
self.compose.can_fail(None, '*', 'build-image')
|
||||||
|
|
||||||
|
self.compose.write_status('FINISHED')
|
||||||
|
|
||||||
|
self.logger.log.assert_has_calls(
|
||||||
|
[mock.call(20, 'Failed build-image on variant <>, arch <*>.'),
|
||||||
|
mock.call(20, 'Failed live on variant <Server>, arch <x86_64>.')],
|
||||||
|
any_order=True)
|
||||||
|
|
||||||
|
with open(os.path.join(self.tmp_dir, 'STATUS'), 'r') as f:
|
||||||
|
self.assertEqual(f.read(), 'FINISHED_INCOMPLETE\n')
|
||||||
|
|
||||||
|
def test_calls_notifier(self):
|
||||||
|
self.compose.notifier = mock.Mock()
|
||||||
|
self.compose.write_status('FINISHED')
|
||||||
|
|
||||||
|
self.assertTrue(self.compose.notifier.send.call_count, 1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user