Repository: Azure/WALinuxAgent
Pull request #2487 (2022-01-26 17:42:53)

Modify deny-listing logic of the Agent
<!-- DO NOT DELETE THIS TEMPLATE -->

## Description

This PR tackles the following - 
- Currently the agent blacklists the agent in 3 scenarios. This PR reduces it to a single place
- Add a new flag (`reason`) in the `error.json` file to differentiate between new blacklisting logic and old
- Un-blacklist the agents that were blacklisted by the legacy agents in order to clean the state

Issue # <!-- if any -->
<!--
Please add an informative description that covers that changes made by the pull request. 
This checklist is used to make sure that common issues in a pull request are addressed.
This will expedite the process of getting your pull request merged and avoid extra work on your part to fix issues discovered during the review process.
-->

---

### PR information
- [ ] The title of the PR is clear and informative.
- [ ] There are a small number of commits, each of which has an informative message. This means that previously merged commits do not appear in the history of the PR. For information on cleaning up the commits in your pull request, [see this page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).
- [ ] If applicable, the PR references the bug/issue that it fixes in the description.
- [ ] New Unit tests were added for the changes made

### Quality of Code and Contribution Guidelines
- [ ] I have read the [contribution guidelines](https://github.com/Azure/WALinuxAgent/blob/master/.github/CONTRIBUTING.md).

codecov[bot]: # [Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) Report
> Merging [#2487](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (f6d9df0) into [develop](https://codecov.io/gh/Azure/WALinuxAgent/commit/2b6dc403b9cf1e5267aa3ed503a63d05d6bd9bf6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (2b6dc40) will **increase** coverage by `0.01%`.
> The diff coverage is `75.00%`.

[![Impacted file tree graph](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487/graphs/tree.svg?width=650&height=150&src=pr&token=ae90E2KYCw&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)

```diff
@@             Coverage Diff             @@
##           develop    #2487      +/-   ##
===========================================
+ Coverage    71.96%   71.97%   +0.01%     
===========================================
  Files          101      101              
  Lines        15064    15084      +20     
  Branches      2394     2398       +4     
===========================================
+ Hits         10841    10857      +16     
- Misses        3737     3742       +5     
+ Partials       486      485       -1     
```


| [Impacted Files](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) | Coverage Δ | |
|---|---|---|
| [azurelinuxagent/ga/update.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2dhL3VwZGF0ZS5weQ==) | `88.30% <75.00%> (-0.50%)` | :arrow_down: |
| [azurelinuxagent/common/event.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2NvbW1vbi9ldmVudC5weQ==) | `86.03% <0.00%> (+0.67%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure). Last update [2b6dc40...f6d9df0](https://codecov.io/gh/Azure/WALinuxAgent/pull/2487?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).


Patch:
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index d795d26b..3c7b6f0a 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -265,7 +265,13 @@ class UpdateHandler(object):
                     log_event=False)
 
                 if ret is None:
-                    ret = self.child_process.wait()
+                    # Wait for the process to exit
+                    if self.child_process.wait() > 0:
+                        msg = u"ExtHandler process {0} launched with command '{1}' exited with return code: {2}".format(
+                            agent_name,
+                            agent_cmd,
+                            ret)
+                        logger.warn(msg)
 
             else:
                 msg = u"Agent {0} launched with command '{1}' failed with return code: {2}".format(
@@ -280,15 +286,6 @@ class UpdateHandler(object):
                     is_success=False,
                     message=msg)
 
-            if ret is not None and ret > 0:
-                msg = u"Agent {0} launched with command '{1}' returned code: {2}".format(
-                    agent_name,
-                    agent_cmd,
-                    ret)
-                logger.warn(msg)
-                if latest_agent is not None:
-                    latest_agent.mark_failure(is_fatal=True)
-
         except Exception as e:
             # Ignore child errors during termination
             if self.is_running:
@@ -304,7 +301,7 @@ class UpdateHandler(object):
                     is_success=False,
                     message=detailed_message)
                 if latest_agent is not None:
-                    latest_agent.mark_failure(is_fatal=True)
+                    latest_agent.mark_failure(is_fatal=True, reason=detailed_message)
 
         self.child_process = None
         return
@@ -368,6 +365,7 @@ class UpdateHandler(object):
             self._ensure_extension_telemetry_state_configured_properly(protocol)
             self._ensure_firewall_rules_persisted(dst_ip=protocol.get_endpoint())
             self._add_accept_tcp_firewall_rule_if_not_enabled(dst_ip=protocol.get_endpoint())
+            self._reset_legacy_blacklisted_agents()
 
             # Get all thread handlers
             telemetry_handler = get_send_telemetry_events_handler(self.protocol_util)
@@ -482,11 +480,11 @@ class UpdateHandler(object):
                     # (unless the CURRENT_VERSION == daemon version, but since we don't support downgrading
                     # below daemon version, we will never reach this code path if that's the scenario)
                     current_agent = next(agent for agent in self.agents if agent.version == CURRENT_VERSION)
-                    logger.info(
-                        "Blacklisting the agent {0} since a downgrade was requested in the GoalState, "
-                        "suggesting that we really don't want to execute any extensions using this version".format(
-                            CURRENT_VERSION))
-                    current_agent.mark_failure(is_fatal=True)
+                    msg = "Blacklisting the agent {0} since a downgrade was requested in the GoalState, " \
+                          "suggesting that we really don't want to execute any extensions using this version".format(
+                           CURRENT_VERSION)
+                    logger.info(msg)
+                    current_agent.mark_failure(is_fatal=True, reason=msg)
                 except StopIteration:
                     logger.warn(
                         "Could not find a matching agent with current version {0} to blacklist, skipping it".format(
@@ -1092,7 +1090,11 @@ class UpdateHandler(object):
             # Set the agents to those available for download at least as current as the existing agent
             # or to the requested version (if specified)
             host = self._get_host_plugin(protocol=protocol)
-            self._set_and_sort_agents([GuestAgent(pkg=pkg, host=host) for pkg in packages_to_download])
+            agents_to_download = [GuestAgent(pkg=pkg, host=host) for pkg in packages_to_download]
+
+            # Filter out the agents that were downloaded/extracted successfully. If the agent was not installed properly,
+            # we delete the directory and the zip package from the filesystem
+            self._set_and_sort_agents([agent for agent in agents_to_download if agent.is_available])
 
             # Remove from disk any agent no longer needed in the VM.
             # If requested version is provided, this would delete all other agents present on the VM except -
@@ -1333,6 +1335,19 @@ class UpdateHandler(object):
                 upgrade_type == AgentUpgradeType.Normal and next_normal_time <= now):
             raise AgentUpgradeExitException(upgrade_message)
 
+    def _reset_legacy_blacklisted_agents(self):
+        # Reset the state of all blacklisted agents that were blacklisted by legacy agents (i.e. not during auto-update)
+
+        # Filter legacy agents which are blacklisted but do not contain a `reason` in their error.json files
+        # (this flag signifies that this agent was blacklisted by the newer agents).
+        try:
+            legacy_blacklisted_agents = [agent for agent in self._load_agents() if
+                                         agent.is_blacklisted and agent.error.reason == '']
+            for agent in legacy_blacklisted_agents:
+                agent.clear_error()
+        except Exception as err:
+            logger.warn("Unable to reset legacy blacklisted agents due to: {0}".format(err))
+
 
 class GuestAgent(object):
     def __init__(self, path=None, pkg=None, host=None):
@@ -1341,13 +1356,13 @@ class GuestAgent(object):
         version = None
         if path is not None:
             m = AGENT_DIR_PATTERN.match(path)
-            if m == None:
+            if m is None:
                 raise UpdateError(u"Illegal agent directory: {0}".format(path))
             version = m.group(1)
         elif self.pkg is not None:
             version = pkg.version
 
-        if version == None:
+        if version is None:
             raise UpdateError(u"Illegal agent version: {0}".format(version))
         self.version = FlexibleVersion(version)
 
@@ -1371,10 +1386,15 @@ class GuestAgent(object):
             if isinstance(e, IOError):
                 raise
 
-            # Note the failure, blacklist the agent if the package downloaded
-            # - An exception with a downloaded package indicates the package
-            #   is corrupt (e.g., missing the HandlerManifest.json file)
-            self.mark_failure(is_fatal=os.path.isfile(self.get_agent_pkg_path()))
+            # If we're unable to download/unpack the agent, delete the Agent directory and the zip file (if exists) to
+            # ensure we try downloading again in the next round.
+            try:
+                if os.path.isdir(self.get_agent_dir()):
+                    shutil.rmtree(self.get_agent_dir(), ignore_errors=True)
+                if os.path.isfile(self.get_agent_pkg_path()):
+                    os.remove(self.get_agent_pkg_path())
+            except Exception as err:
+                logger.warn("Unable to delete Agent files: {0}".format(err))
 
             msg = u"Agent {0} install failed with exception:".format(
                 self.name)
@@ -1422,11 +1442,11 @@ class GuestAgent(object):
         return self.is_blacklisted or \
                os.path.isfile(self.get_agent_manifest_path())
 
-    def mark_failure(self, is_fatal=False):
+    def mark_failure(self, is_fatal=False, reason=''):
         try:
             if not os.path.isdir(self.get_agent_dir()):
                 os.makedirs(self.get_agent_dir())
-            self.error.mark_failure(is_fatal=is_fatal)
+            self.error.mark_failure(is_fatal=is_fatal, reason=reason)
             self.error.save()
             if self.error.is_blacklisted:
                 msg = u"Agent {0} is permanently blacklisted".format(self.name)
@@ -1617,23 +1637,29 @@ class GuestAgent(object):
 
 class GuestAgentError(object):
     def __init__(self, path):
+        self.last_failure = 0.0
+        self.was_fatal = False
         if path is None:
             raise UpdateError(u"GuestAgentError requires a path")
         self.path = path
+        self.failure_count = 0
+        self.reason = ''
 
         self.clear()
         return
 
-    def mark_failure(self, is_fatal=False):
-        self.last_failure = time.time()  # pylint: disable=W0201
+    def mark_failure(self, is_fatal=False, reason=''):
+        self.last_failure = time.time()
         self.failure_count += 1
-        self.was_fatal = is_fatal  # pylint: disable=W0201
+        self.was_fatal = is_fatal
+        self.reason = reason
         return
 
     def clear(self):
         self.last_failure = 0.0
         self.failure_count = 0
         self.was_fatal = False
+        self.reason = ''
         return
 
     @property
@@ -1665,25 +1691,25 @@ class GuestAgentError(object):
         return
 
     def from_json(self, data):
-        self.last_failure = max(  # pylint: disable=W0201
-            self.last_failure,
-            data.get(u"last_failure", 0.0))
-        self.failure_count = max(  # pylint: disable=W0201
-            self.failure_count,
-            data.get(u"failure_count", 0))
-        self.was_fatal = self.was_fatal or data.get(u"was_fatal", False)  # pylint: disable=W0201
+        self.last_failure = max(self.last_failure, data.get(u"last_failure", 0.0))
+        self.failure_count = max(self.failure_count, data.get(u"failure_count", 0))
+        self.was_fatal = self.was_fatal or data.get(u"was_fatal", False)
+        reason = data.get(u"reason", '')
+        self.reason = reason if reason != '' else self.reason
         return
 
     def to_json(self):
         data = {
             u"last_failure": self.last_failure,
             u"failure_count": self.failure_count,
-            u"was_fatal": self.was_fatal
+            u"was_fatal": self.was_fatal,
+            u"reason": ustr(self.reason)
         }
         return data
 
     def __str__(self):
-        return "Last Failure: {0}, Total Failures: {1}, Fatal: {2}".format(
+        return "Last Failure: {0}, Total Failures: {1}, Fatal: {2}, Reason: {3}".format(
             self.last_failure,
             self.failure_count,
-            self.was_fatal)
+            self.was_fatal,
+            self.reason)


Test patch:
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index e1dbe55b..9cc5bdb1 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -15,6 +15,7 @@ import sys
 import tempfile
 import time
 import unittest
+import uuid
 import zipfile
 
 from datetime import datetime, timedelta
@@ -59,19 +60,22 @@ from tests.protocol.HttpRequestPredicates import HttpRequestPredicates
 NO_ERROR = {
     "last_failure": 0.0,
     "failure_count": 0,
-    "was_fatal": False
+    "was_fatal": False,
+    "reason": ''
 }
 
 FATAL_ERROR = {
     "last_failure": 42.42,
     "failure_count": 2,
-    "was_fatal": True
+    "was_fatal": True,
+    "reason": "Test failure"
 }
 
 WITH_ERROR = {
     "last_failure": 42.42,
     "failure_count": 2,
-    "was_fatal": False
+    "was_fatal": False,
+    "reason": "Test failure"
 }
 
 EMPTY_MANIFEST = {
@@ -413,17 +417,19 @@ class TestGuestAgentError(UpdateTestCase):
 
     def test_str(self):
         err = self.create_error(error_data=NO_ERROR)
-        s = "Last Failure: {0}, Total Failures: {1}, Fatal: {2}".format(
+        s = "Last Failure: {0}, Total Failures: {1}, Fatal: {2}, Reason: {3}".format(
             NO_ERROR["last_failure"],
             NO_ERROR["failure_count"],
-            NO_ERROR["was_fatal"])
+            NO_ERROR["was_fatal"],
+            NO_ERROR["reason"])
         self.assertEqual(s, str(err))
 
         err = self.create_error(error_data=WITH_ERROR)
-        s = "Last Failure: {0}, Total Failures: {1}, Fatal: {2}".format(
+        s = "Last Failure: {0}, Total Failures: {1}, Fatal: {2}, Reason: {3}".format(
             WITH_ERROR["last_failure"],
             WITH_ERROR["failure_count"],
-            WITH_ERROR["was_fatal"])
+            WITH_ERROR["was_fatal"],
+            WITH_ERROR["reason"])
         self.assertEqual(s, str(err))
         return
 
@@ -727,7 +733,7 @@ class TestGuestAgent(UpdateTestCase):
         self.assertTrue(agent.is_downloaded)
 
     @patch("azurelinuxagent.ga.update.GuestAgent._download", side_effect=UpdateError)
-    def test_ensure_downloaded_download_fails(self, mock_download):  # pylint: disable=unused-argument
+    def test_ensure_failure_in_download_cleans_up_filesystem(self, _):
         self.remove_agents()
         self.assertFalse(os.path.isdir(self.agent_path))
 
@@ -735,36 +741,36 @@ class TestGuestAgent(UpdateTestCase):
         pkg.uris.append(None)
         agent = GuestAgent(pkg=pkg)
 
-        self.assertEqual(1, agent.error.failure_count)
-        self.assertFalse(agent.error.was_fatal)
-        self.assertFalse(agent.is_blacklisted)
+        self.assertFalse(agent.is_blacklisted, "The agent should not be blacklisted if unable to unpack/download")
+        self.assertFalse(os.path.exists(agent.get_agent_dir()), "Agent directory should be cleaned up")
+        self.assertFalse(os.path.exists(agent.get_agent_pkg_path()), "Agent package should be cleaned up")
 
     @patch("azurelinuxagent.ga.update.GuestAgent._download")
     @patch("azurelinuxagent.ga.update.GuestAgent._unpack", side_effect=UpdateError)
-    def test_ensure_downloaded_unpack_fails(self, mock_unpack, mock_download):  # pylint: disable=unused-argument
+    def test_ensure_downloaded_unpack_failure_cleans_file_system(self, *_):
         self.assertFalse(os.path.isdir(self.agent_path))
 
         pkg = ExtHandlerPackage(version=str(self._get_agent_version()))
         pkg.uris.append(None)
         agent = GuestAgent(pkg=pkg)
 
-        self.assertEqual(1, agent.error.failure_count)
-        self.assertTrue(agent.error.was_fatal)
-        self.assertTrue(agent.is_blacklisted)
+        self.assertFalse(agent.is_blacklisted, "The agent should not be blacklisted if unable to unpack/download")
+        self.assertFalse(os.path.exists(agent.get_agent_dir()), "Agent directory should be cleaned up")
+        self.assertFalse(os.path.exists(agent.get_agent_pkg_path()), "Agent package should be cleaned up")
 
     @patch("azurelinuxagent.ga.update.GuestAgent._download")
     @patch("azurelinuxagent.ga.update.GuestAgent._unpack")
     @patch("azurelinuxagent.ga.update.GuestAgent._load_manifest", side_effect=UpdateError)
-    def test_ensure_downloaded_load_manifest_fails(self, mock_manifest, mock_unpack, mock_download):  # pylint: disable=unused-argument
+    def test_ensure_downloaded_load_manifest_cleans_up_agent_directories(self, *_):
         self.assertFalse(os.path.isdir(self.agent_path))
 
         pkg = ExtHandlerPackage(version=str(self._get_agent_version()))
         pkg.uris.append(None)
         agent = GuestAgent(pkg=pkg)
 
-        self.assertEqual(1, agent.error.failure_count)
-        self.assertTrue(agent.error.was_fatal)
-        self.assertTrue(agent.is_blacklisted)
+        self.assertFalse(agent.is_blacklisted, "The agent should not be blacklisted if unable to unpack/download")
+        self.assertFalse(os.path.exists(agent.get_agent_dir()), "Agent directory should be cleaned up")
+        self.assertFalse(os.path.exists(agent.get_agent_pkg_path()), "Agent package should be cleaned up")
 
     @patch("azurelinuxagent.ga.update.GuestAgent._download")
     @patch("azurelinuxagent.ga.update.GuestAgent._unpack")
@@ -1256,8 +1262,7 @@ class TestUpdate(UpdateTestCase):
         finally:
             shutil.rmtree(tempdir, True)
 
-    def test_run_latest_nonzero_code_marks_failures(self):
-        # logger.add_logger_appender(logger.AppenderType.STDOUT)
+    def test_run_latest_nonzero_code_does_not_mark_failure(self):
         self.prepare_agents()
 
         latest_agent = self.update_handler.get_latest_agent_greater_than_daemon()
@@ -1268,10 +1273,7 @@ class TestUpdate(UpdateTestCase):
         with patch('azurelinuxagent.ga.update.UpdateHandler.get_latest_agent_greater_than_daemon', return_value=latest_agent):
             self._test_run_latest(mock_child=ChildMock(return_value=1))
 
-        self.assertTrue(latest_agent.is_blacklisted)
-        self.assertFalse(latest_agent.is_available)
-        self.assertNotEqual(0.0, latest_agent.error.last_failure)
-        self.assertEqual(1, latest_agent.error.failure_count)
+        self.assertFalse(latest_agent.is_blacklisted, "Agent should not be blacklisted")
 
     def test_run_latest_exception_blacklists(self):
         self.prepare_agents()
@@ -1280,14 +1282,16 @@ class TestUpdate(UpdateTestCase):
         self.assertTrue(latest_agent.is_available)
         self.assertEqual(0.0, latest_agent.error.last_failure)
         self.assertEqual(0, latest_agent.error.failure_count)
+        verify_string = "Force blacklisting: {0}".format(str(uuid.uuid4()))
 
         with patch('azurelinuxagent.ga.update.UpdateHandler.get_latest_agent_greater_than_daemon', return_value=latest_agent):
-            self._test_run_latest(mock_child=ChildMock(side_effect=Exception("Force blacklisting")))
+            self._test_run_latest(mock_child=ChildMock(side_effect=Exception(verify_string)))
 
         self.assertFalse(latest_agent.is_available)
         self.assertTrue(latest_agent.error.is_blacklisted)
         self.assertNotEqual(0.0, latest_agent.error.last_failure)
         self.assertEqual(1, latest_agent.error.failure_count)
+        self.assertIn(verify_string, latest_agent.error.reason, "Error reason not found while blacklisting")
 
     def test_run_latest_exception_does_not_blacklist_if_terminating(self):
         self.prepare_agents()
@@ -1869,6 +1873,27 @@ class TestUpdate(UpdateTestCase):
                      "Retrieving the goal state recovered from previous errors" in args[0]]
         self.assertTrue(len(info_msgs) > 0, "Agent should've logged a message when recovered from GS errors")
 
+    def test_it_should_reset_legacy_blacklisted_agents_on_process_start(self):
+        # Add some good agents
+        self.prepare_agents(count=10)
+        good_agents = [agent.name for agent in self.agents()]
+
+        # Add a set of blacklisted agents
+        self.prepare_agents(count=20, is_available=False)
+        for agent in self.agents():
+            # Assert the test environment is correctly set
+            if agent.name not in good_agents:
+                self.assertTrue(agent.is_blacklisted, "Agent {0} should be blacklisted".format(agent.name))
+            else:
+                self.assertFalse(agent.is_blacklisted, "Agent {0} should not be blacklisted".format(agent.name))
+
+        with _get_update_handler() as (update_handler, _):
+            update_handler.run(debug=True)
+            self.assertEqual(20, self.agent_count(), "All agents should be available on disk")
+            # Ensure none of the agents are blacklisted
+            for agent in self.agents():
+                self.assertFalse(agent.is_blacklisted, "Legacy Agent should not be blacklisted")
+
 
 class TestAgentUpgrade(UpdateTestCase):
 
@@ -2292,8 +2317,11 @@ class TestAgentUpgrade(UpdateTestCase):
             self.__assert_exit_code_successful(update_handler.exit_mock)
             self.__assert_upgrade_telemetry_emitted_for_requested_version(mock_telemetry, upgrade=False,
                                                                           version=downgraded_version)
-            self.assertTrue(next(agent for agent in self.agents() if agent.version == CURRENT_VERSION).is_blacklisted,
-                            "The current agent should be blacklisted")
+            current_agent = next(agent for agent in self.agents() if agent.version == CURRENT_VERSION)
+            self.assertTrue(current_agent.is_blacklisted, "The current agent should be blacklisted")
+            self.assertEqual(current_agent.error.reason, "Blacklisting the agent {0} since a downgrade was requested in the GoalState, "
+                                                         "suggesting that we really don't want to execute any extensions using this version".format(CURRENT_VERSION),
+                             "Invalid reason specified for blacklisting agent")
 
     def test_it_should_not_downgrade_below_daemon_version(self):
         data_file = mockwiredata.DATA_FILE.copy()