From f3abacce486ed0b04c3c5ce5069d65315f7d0d4b Mon Sep 17 00:00:00 2001 From: Michiel Holtkamp Date: Fri, 11 Oct 2019 12:20:13 +0000 Subject: [PATCH] NEW: Create a release object if necessary --- katka/signals.py | 28 ++++++++- tests/integration/conftest.py | 16 ++--- tests/integration/test_signals.py | 100 +++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/katka/signals.py b/katka/signals.py index a817816..36efed3 100644 --- a/katka/signals.py +++ b/katka/signals.py @@ -1,10 +1,14 @@ +import logging + from django.conf import settings from django.db.models.signals import post_save from django.dispatch import receiver -from .constants import PIPELINE_STATUS_INITIALIZING, STEP_FINAL_STATUSES +from .constants import PIPELINE_STATUS_INITIALIZING, RELEASE_STATUS_OPEN, STEP_FINAL_STATUSES from .fields import username_on_model -from .models import SCMPipelineRun, SCMStepRun +from .models import SCMPipelineRun, SCMRelease, SCMStepRun + +log = logging.getLogger('katka') @receiver(post_save, sender=SCMStepRun) @@ -29,6 +33,9 @@ def update_pipeline_from_steps(sender, **kwargs): @receiver(post_save, sender=SCMPipelineRun) def send_pipeline_change_notification(sender, **kwargs): pipeline = kwargs['instance'] + if kwargs['created'] is True: + create_release_if_necessary(pipeline) + if pipeline.status == PIPELINE_STATUS_INITIALIZING and kwargs['created'] is False: # Do not send notifications when the pipeline is initializing. While initializing, steps are created and # since this is done with several requests, several notifications would be sent, while the only one you @@ -41,3 +48,20 @@ def send_pipeline_change_notification(sender, **kwargs): session.post( settings.PIPELINE_CHANGE_NOTIFICATION_URL, json={'public_identifier': str(pipeline.public_identifier)} ) + + +def create_release_if_necessary(pipeline): + releases = SCMRelease.objects.filter( + status=RELEASE_STATUS_OPEN, scm_pipeline_runs__application=pipeline.application + ) + with username_on_model(SCMRelease, pipeline.modified_username): + if len(releases) == 0: + release = SCMRelease.objects.create() + elif len(releases) > 1: + log.error(f'Multiple open releases found for application {pipeline.application.pk}, picking newest') + release = releases.order_by('-created').first() + else: + release = releases[0] + + release.scm_pipeline_runs.add(pipeline) + release.save() diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index febd6ae..9122ac7 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -378,13 +378,13 @@ def deactivated_scm_step_run(scm_step_run): @pytest.fixture def scm_release(scm_pipeline_run): - scm_release = models.SCMRelease(name='Version 0.13.1', - from_hash='577fe3f6a091aa4bad996623b1548b87f4f9c1f8', - to_hash='a49954f060b1b7605e972c9448a74d4067547443') + scm_release = models.SCMRelease.objects.filter(scm_pipeline_runs__pk__exact=scm_pipeline_run.pk).first() with username_on_model(models.SCMRelease, 'initial'): + scm_release.name = 'Version 0.13.1' + scm_release.from_hash = '577fe3f6a091aa4bad996623b1548b87f4f9c1f8' + scm_release.to_hash = 'a49954f060b1b7605e972c9448a74d4067547443' scm_release.save() - scm_release.scm_pipeline_runs.set([scm_pipeline_run]) return scm_release @@ -400,13 +400,13 @@ def deactivated_scm_release(scm_release): @pytest.fixture def another_scm_release(another_scm_pipeline_run): - scm_release = models.SCMRelease(name='Version 15.0', - from_hash='100763d7144e1f993289bd528dc698dd3906a807', - to_hash='38d72050370e6e0b43df649c9630f7135ef6de0d') + scm_release = models.SCMRelease.objects.filter(scm_pipeline_runs__pk__exact=another_scm_pipeline_run.pk).first() with username_on_model(models.SCMRelease, 'initial'): + scm_release.name = 'Version 15.0' + scm_release.from_hash = '100763d7144e1f993289bd528dc698dd3906a807' + scm_release.to_hash = '38d72050370e6e0b43df649c9630f7135ef6de0d' scm_release.save() - scm_release.scm_pipeline_runs.set([another_scm_pipeline_run]) return scm_release diff --git a/tests/integration/test_signals.py b/tests/integration/test_signals.py index e5589ab..2352fab 100644 --- a/tests/integration/test_signals.py +++ b/tests/integration/test_signals.py @@ -4,7 +4,7 @@ import pytest from katka import constants from katka.fields import username_on_model -from katka.models import SCMPipelineRun, SCMStepRun +from katka.models import SCMPipelineRun, SCMRelease, SCMStepRun @pytest.mark.django_db @@ -97,3 +97,101 @@ def test_send_on_create_pipeline(self, application): pipeline_run.save() assert len(session.post.call_args_list) == 2 + + +@pytest.mark.django_db +class TestReleaseSignal: + def test_release_created_when_pipeline_run_created(self, application): + assert SCMRelease.objects.count() == 0 + + session = mock.MagicMock() + overrides = {'PIPELINE_CHANGE_NOTIFICATION_SESSION': session} + with override_settings(**overrides), username_on_model(SCMPipelineRun, 'signal_tester'): + pipeline_run = SCMPipelineRun.objects.create(application=application) + + assert SCMRelease.objects.count() == 1 + assert SCMRelease.objects.filter(scm_pipeline_runs__pk__exact=pipeline_run.pk).count() == 1 + + def test_release_not_created_when_updating(self, scm_pipeline_run): + session = mock.MagicMock() + overrides = {'PIPELINE_CHANGE_NOTIFICATION_SESSION': session} + + # before + assert SCMRelease.objects.count() == 1 + with override_settings(**overrides), username_on_model(SCMPipelineRun, 'signal_tester'): + scm_pipeline_run.status = 'success' + scm_pipeline_run.save() + + # after + assert SCMRelease.objects.count() == 1 + + def test_new_pipeline_gets_added_to_open_release(self, scm_pipeline_run, application): + """When a release is still open, NO new release should be created when a new pipeline is created""" + assert SCMRelease.objects.count() == 1 + + session = mock.MagicMock() + overrides = {'PIPELINE_CHANGE_NOTIFICATION_SESSION': session} + with override_settings(**overrides), username_on_model(SCMPipelineRun, 'signal_tester'): + pipeline_run = SCMPipelineRun.objects.create(application=application) + + assert SCMRelease.objects.count() == 1 + release = SCMRelease.objects.first() + assert len(release.scm_pipeline_runs.all()) == 2 + assert release.scm_pipeline_runs.filter(pk__exact=scm_pipeline_run.pk).count() == 1 + assert release.scm_pipeline_runs.filter(pk__exact=pipeline_run.pk).count() == 1 + + def test_new_pipeline_gets_added_to_new_release(self, scm_pipeline_run, scm_release, application): + """When a release is closed, a new release should be created when a new pipeline is created""" + assert SCMRelease.objects.count() == 1 + + session = mock.MagicMock() + overrides = {'PIPELINE_CHANGE_NOTIFICATION_SESSION': session} + with override_settings(**overrides), username_on_model(SCMRelease, 'signal_tester'): + scm_release.status = 'closed' + scm_release.save() + + with override_settings(**overrides), username_on_model(SCMPipelineRun, 'signal_tester'): + pipeline_run = SCMPipelineRun.objects.create(application=application) + + assert SCMRelease.objects.count() == 2 + release = SCMRelease.objects.filter(scm_pipeline_runs__pk__exact=pipeline_run.pk).first() + assert release.pk != scm_release.pk + + assert len(scm_release.scm_pipeline_runs.all()) == 1 + assert scm_release.scm_pipeline_runs.filter(pk__exact=scm_pipeline_run.pk).count() == 1 + + assert len(release.scm_pipeline_runs.all()) == 1 + assert release.scm_pipeline_runs.filter(pk__exact=pipeline_run.pk).count() == 1 + + def test_multiple_applications(self, scm_pipeline_run, another_scm_pipeline_run, scm_release, another_scm_release): + """Different releases should be created for pipeline_runs of different applications""" + assert SCMRelease.objects.count() == 2 + assert another_scm_release.pk != scm_release.pk + + assert len(scm_release.scm_pipeline_runs.all()) == 1 + assert scm_release.scm_pipeline_runs.filter(pk__exact=scm_pipeline_run.pk).count() == 1 + + assert len(another_scm_release.scm_pipeline_runs.all()) == 1 + assert another_scm_release.scm_pipeline_runs.filter(pk__exact=another_scm_pipeline_run.pk).count() == 1 + + def test_pick_newest_on_duplicate_open_releases(self, application, scm_release, scm_pipeline_run, caplog): + """This should not happen, but if it happens, handle it gracefully""" + session = mock.MagicMock() + overrides = {'PIPELINE_CHANGE_NOTIFICATION_SESSION': session} + with override_settings(**overrides), username_on_model(SCMRelease, 'signal_tester'): + open_release_2 = SCMRelease.objects.create() + open_release_2.scm_pipeline_runs.add(scm_pipeline_run) + open_release_2.save() + + assert scm_release.created < open_release_2.created + + with override_settings(**overrides), username_on_model(SCMPipelineRun, 'signal_tester'): + pipeline_run = SCMPipelineRun.objects.create(application=application) + + assert SCMRelease.objects.count() == 2 + assert not scm_release.scm_pipeline_runs.filter(pk=pipeline_run.pk).exists() + assert open_release_2.scm_pipeline_runs.filter(pk=pipeline_run.pk).exists() + katka_records = [record.message for record in caplog.records if record.name == 'katka'] + assert len(katka_records) == 1 + assert 'Multiple open releases found' in katka_records[0] + assert 'picking newest' in katka_records[0]