From 36bb02ddfa3ebfbc6f0761b88bf0a87e62c17d0b Mon Sep 17 00:00:00 2001
From: Jan Kaluza <jkaluza@redhat.com>
Date: Sep 27 2017 11:22:49 +0000
Subject: Initiate rebuild of advisory related artifacts also when advisory state changes.


---

diff --git a/freshmaker/errata.py b/freshmaker/errata.py
index a40513c..97faef1 100644
--- a/freshmaker/errata.py
+++ b/freshmaker/errata.py
@@ -24,7 +24,7 @@
 import requests
 from requests_kerberos import HTTPKerberosAuth
 
-from freshmaker.events import BrewSignRPMEvent
+from freshmaker.events import BrewSignRPMEvent, ErrataAdvisoryStateChangedEvent
 
 
 class ErrataAdvisory(object):
@@ -103,6 +103,13 @@ class Errata(object):
                 advisories.append(advisory)
 
             return advisories
+        elif isinstance(event, ErrataAdvisoryStateChangedEvent):
+            data = self._errata_http_get(
+                "advisory/%s.json" % str(event.errata_id))
+            advisory = ErrataAdvisory(
+                data["id"], data["advisory_name"], data["status"],
+                data["security_impact"])
+            return [advisory]
         else:
             raise ValueError("Unsupported event type")
 
diff --git a/freshmaker/handlers/errata/errata_advisory_state_changed.py b/freshmaker/handlers/errata/errata_advisory_state_changed.py
index 68d70d5..60f311a 100644
--- a/freshmaker/handlers/errata/errata_advisory_state_changed.py
+++ b/freshmaker/handlers/errata/errata_advisory_state_changed.py
@@ -19,12 +19,12 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-from freshmaker import log
-from freshmaker import db
+from freshmaker import db, conf, log
 from freshmaker.events import (
     ErrataAdvisoryStateChangedEvent, ErrataAdvisoryRPMsSignedEvent)
 from freshmaker.models import Event, EVENT_TYPES
 from freshmaker.handlers import BaseHandler
+from freshmaker.errata import Errata
 
 
 class ErrataAdvisoryStateChangedHandler(BaseHandler):
@@ -43,15 +43,11 @@ class ErrataAdvisoryStateChangedHandler(BaseHandler):
     def can_handle(self, event):
         return isinstance(event, ErrataAdvisoryStateChangedEvent)
 
-    def handle(self, event):
-        errata_id = event.errata_id
-        state = event.state
-        if state != "SHIPPED_LIVE":
-            log.debug("Skipping Errata advisory %d to be marked as released, "
-                      "because its state is %s rather than SHIPPED_LIVE.",
-                      errata_id, state)
-            return []
-
+    def mark_as_released(self, errata_id):
+        """
+        Marks the Errata advisory with `errata_id` ID as "released", so it
+        is not included in further container images rebuilds.
+        """
         # check db to see whether this advisory exists in db
         db_event = db.session.query(Event).filter_by(
             event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent],
@@ -64,3 +60,50 @@ class ErrataAdvisoryStateChangedHandler(BaseHandler):
         db_event.released = True
         db.session.commit()
         log.info("Errata advisory %d is now marked as released", errata_id)
+
+    def rebuild_if_not_exists(self, event, errata_id):
+        """
+        Initiates rebuild of artifacts based on Errata advisory with
+        `errata_id` id.
+
+        :rtype: List of ErrataAdvisoryRPMsSignedEvent instances.
+        :return: List of extra events generated to initiate the rebuild.
+        """
+
+        db_event = db.session.query(Event).filter_by(
+            event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent],
+            search_key=str(errata_id)).one_or_none()
+        if db_event:
+            log.debug("Ignoring Errata advisory %d - it already exists in "
+                      "Freshmaker db.", errata_id)
+            return []
+
+        # Get additional info from Errata to fill in the needed data.
+        errata = Errata(conf.errata_tool_server_url)
+        advisories = errata.advisories_from_event(event)
+        if not advisories:
+            log.error("Unknown Errata advisory %d" % errata_id)
+            return []
+
+        log.info("Generating ErrataAdvisoryRPMsSignedEvent for Errata "
+                 "advisory %d, because its state changed to %s.", errata_id,
+                 event.state)
+        advisory = advisories[0]
+        db_event = ErrataAdvisoryRPMsSignedEvent(
+                event.msg_id + "." + str(advisory.name), advisory.name,
+                advisory.errata_id, advisory.security_impact)
+        return [db_event]
+
+    def handle(self, event):
+        errata_id = event.errata_id
+        state = event.state
+
+        extra_events = []
+
+        if state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]:
+            extra_events += self.rebuild_if_not_exists(event, errata_id)
+
+        if state == "SHIPPED_LIVE":
+            self.mark_as_released(errata_id)
+
+        return extra_events
diff --git a/tests/test_errata.py b/tests/test_errata.py
index 0f75b35..26b784f 100644
--- a/tests/test_errata.py
+++ b/tests/test_errata.py
@@ -25,7 +25,8 @@ import unittest
 from mock import patch
 
 from freshmaker.errata import Errata
-from freshmaker.events import BrewSignRPMEvent, GitRPMSpecChangeEvent
+from freshmaker.events import (
+    BrewSignRPMEvent, GitRPMSpecChangeEvent, ErrataAdvisoryStateChangedEvent)
 
 
 class MockedErrataAPI(object):
@@ -68,7 +69,11 @@ class MockedErrataAPI(object):
             "all_errata": [{"id": 28484, "name": "RHSA-2017:28484", "status": "QE"}],
             "rpms_signed": True}
 
-        self.advisory_json = {"security_impact": "Important"}
+        self.advisory_json = {
+            "id": 28484,
+            "advisory_name": "RHSA-2017:28484",
+            "status": "QE",
+            "security_impact": "Important"}
 
     def errata_rest_get(self, endpoint):
         if endpoint.find("build/") != -1:
@@ -112,6 +117,16 @@ class TestErrata(unittest.TestCase):
 
     @patch.object(Errata, "_errata_rest_get")
     @patch.object(Errata, "_errata_http_get")
+    def test_advisories_from_event_errata_state_change_event(
+            self, errata_http_get, errata_rest_get):
+        MockedErrataAPI(errata_rest_get, errata_http_get)
+        event = ErrataAdvisoryStateChangedEvent("msgid", 28484, "SHIPPED_LIVE")
+        advisories = self.errata.advisories_from_event(event)
+        self.assertEqual(len(advisories), 1)
+        self.assertEqual(advisories[0].errata_id, 28484)
+
+    @patch.object(Errata, "_errata_rest_get")
+    @patch.object(Errata, "_errata_http_get")
     def test_builds_signed_all_signed(self, errata_http_get, errata_rest_get):
         MockedErrataAPI(errata_rest_get, errata_http_get)
         self.assertTrue(self.errata.builds_signed(28484))
diff --git a/tests/test_errata_advisory_state_changed.py b/tests/test_errata_advisory_state_changed.py
index 9a5d074..b9c1cea 100644
--- a/tests/test_errata_advisory_state_changed.py
+++ b/tests/test_errata_advisory_state_changed.py
@@ -27,7 +27,10 @@ import json
 from mock import patch, MagicMock, PropertyMock, Mock
 
 from freshmaker.handlers.errata import ErrataAdvisoryRPMsSignedHandler
+from freshmaker.handlers.errata import ErrataAdvisoryStateChangedHandler
 from freshmaker.events import ErrataAdvisoryRPMsSignedEvent
+from freshmaker.events import ErrataAdvisoryStateChangedEvent
+from freshmaker.errata import ErrataAdvisory
 
 from freshmaker import db, events
 from freshmaker.models import Event, ArtifactBuild
@@ -462,3 +465,110 @@ class TestFindEventsToInclude(unittest.TestCase):
 
         self.assertEqual(len(events), 1)
         self.assertEqual(events[0].search_key, "old_event_foo")
+
+
+class TestErrataAdvisoryStateChangedHandler(unittest.TestCase):
+
+    def setUp(self):
+        db.session.remove()
+        db.drop_all()
+        db.create_all()
+        db.session.commit()
+
+    def tearDown(self):
+        db.session.remove()
+        db.drop_all()
+        db.session.commit()
+
+    @patch('freshmaker.errata.Errata.advisories_from_event')
+    def test_rebuild_if_not_exists(self, advisories_from_event):
+        handler = ErrataAdvisoryStateChangedHandler()
+
+        for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]:
+            advisories_from_event.return_value = [
+                ErrataAdvisory(123, "RHSA-2017", state, "Critical")]
+            ev = ErrataAdvisoryStateChangedEvent("msg123", 123, state)
+            ret = handler.handle(ev)
+
+            self.assertEqual(len(ret), 1)
+            self.assertEqual(ret[0].errata_id, 123)
+            self.assertEqual(ret[0].security_impact, "Critical")
+            self.assertEqual(ret[0].errata_name, "RHSA-2017")
+
+    @patch('freshmaker.errata.Errata.advisories_from_event')
+    def test_rebuild_if_not_exists_unknown_states(
+            self, advisories_from_event):
+        handler = ErrataAdvisoryStateChangedHandler()
+
+        for state in ["NEW_FILES", "QE", "UNKNOWN"]:
+            advisories_from_event.return_value = [
+                ErrataAdvisory(123, "RHSA-2017", state, "Critical")]
+            ev = ErrataAdvisoryStateChangedEvent("msg123", 123, state)
+            ret = handler.handle(ev)
+
+            self.assertEqual(len(ret), 0)
+
+    @patch('freshmaker.errata.Errata.advisories_from_event')
+    def test_rebuild_if_not_exists_already_exists(
+            self, advisories_from_event):
+        handler = ErrataAdvisoryStateChangedHandler()
+
+        Event.create(
+            db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent)
+        db.session.commit()
+
+        for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]:
+            advisories_from_event.return_value = [
+                ErrataAdvisory(123, "RHSA-2017", state, "Critical")]
+            ev = ErrataAdvisoryStateChangedEvent("msg123", 123, state)
+            ret = handler.handle(ev)
+
+            self.assertEqual(len(ret), 0)
+
+    @patch('freshmaker.errata.Errata.advisories_from_event')
+    def test_rebuild_if_not_exists_unknown_errata_id(
+            self, advisories_from_event):
+        advisories_from_event.return_value = []
+        handler = ErrataAdvisoryStateChangedHandler()
+
+        for state in ["REL_PREP", "PUSH_READY", "IN_PUSH", "SHIPPED_LIVE"]:
+            ev = ErrataAdvisoryStateChangedEvent("msg123", 123, state)
+            ret = handler.handle(ev)
+
+            self.assertEqual(len(ret), 0)
+
+    def test_mark_as_released(self):
+        db_event = Event.create(
+            db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent, False)
+        db.session.commit()
+
+        self.assertEqual(db_event.released, False)
+
+        ev = ErrataAdvisoryStateChangedEvent("msg123", 123, "SHIPPED_LIVE")
+
+        handler = ErrataAdvisoryStateChangedHandler()
+        handler.handle(ev)
+
+        db.session.refresh(db_event)
+        self.assertEqual(db_event.released, True)
+
+    def test_mark_as_released_wrong_advisory_status(self):
+        db_event = Event.create(
+            db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent, False)
+        db.session.commit()
+
+        for state in ["NEW_FILES", "QE", "REL_PREP", "PUSH_READY", "IN_PUSH"]:
+            ev = ErrataAdvisoryStateChangedEvent("msg123", 123, state)
+
+            handler = ErrataAdvisoryStateChangedHandler()
+            handler.handle(ev)
+
+            db.session.refresh(db_event)
+            self.assertEqual(db_event.released, False)
+
+    @patch('freshmaker.errata.Errata.advisories_from_event')
+    def test_mark_as_released_unknown_event(self, advisories_from_event):
+        ev = ErrataAdvisoryStateChangedEvent("msg123", 123, "SHIPPED_LIVE")
+
+        handler = ErrataAdvisoryStateChangedHandler()
+        handler.handle(ev)

