Repository: Azure/WALinuxAgent
Pull request #3346 (2025-04-15 22:45:48)

Disable rsm downgrade
<!-- DO NOT DELETE THIS TEMPLATE -->

## Description
The scenarios like reimage, service heal causing unintended agent downgrades for rsm vms, which requires fixes in multiple components (From RSM to Agent). Disabling the downgrade path in next release, re enable it once components are fixed and validated.


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
- [ ] Ensure development PR is based on the `develop` branch.
- [ ] 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).


Patch:
diff --git a/azurelinuxagent/common/conf.py b/azurelinuxagent/common/conf.py
index 75dd81ec..45b739c3 100644
--- a/azurelinuxagent/common/conf.py
+++ b/azurelinuxagent/common/conf.py
@@ -707,3 +707,11 @@ def get_log_collector_initial_delay(conf=__conf__):
     NOTE: This option is experimental and may be removed in later versions of the Agent.
     """
     return conf.get_int("Debug.LogCollectorInitialDelay", 5 * 60)
+
+def get_enable_rsm_downgrade(conf=__conf__):
+    """
+    If False, the agent will not downgrade to a lower version when a lower version is requested in the goal state.
+
+    Todo: Flag will be removed once we have a fix for rsm downgrade scenario.
+    """
+    return conf.get_switch("Debug.EnableRsmDowngrade", False)
diff --git a/azurelinuxagent/ga/rsm_version_updater.py b/azurelinuxagent/ga/rsm_version_updater.py
index a2fb00d9..54b48e5f 100644
--- a/azurelinuxagent/ga/rsm_version_updater.py
+++ b/azurelinuxagent/ga/rsm_version_updater.py
@@ -88,9 +88,11 @@ class RSMVersionUpdater(GAVersionUpdater):
         """
         Once version retrieved from goal state, we check if we allowed to update for that version
         allow update If new version not same as current version, not below than daemon version and if version is from rsm request
+
+        Todo: Downgrade flow has issues, not allowing it until we have a fix
         """
 
-        if not agent_family.is_version_from_rsm or self._version < self._daemon_version or self._version == CURRENT_VERSION:
+        if not agent_family.is_version_from_rsm or self._version < self._daemon_version or self._version == CURRENT_VERSION or (self._version < CURRENT_VERSION and not conf.get_enable_rsm_downgrade()):
             return False
 
         return True


Test patch:
diff --git a/tests/data/wire/ext_conf_version_missing_in_manifest.xml b/tests/data/wire/ext_conf_version_missing_in_manifest.xml
index 4d3ebd70..a4a30d5f 100644
--- a/tests/data/wire/ext_conf_version_missing_in_manifest.xml
+++ b/tests/data/wire/ext_conf_version_missing_in_manifest.xml
@@ -3,7 +3,7 @@
         <GAFamilies>
             <GAFamily>
                 <Name>Prod</Name>
-                <Version>5.2.1.0</Version>
+                <Version>9.9.9.999</Version>
                 <IsVersionFromRSM>True</IsVersionFromRSM>
                 <IsVMEnabledForRSMUpgrades>True</IsVMEnabledForRSMUpgrades>
                 <Uris>
@@ -12,7 +12,7 @@
             </GAFamily>
             <GAFamily>
                 <Name>Test</Name>
-                <Version>5.2.1.0</Version>
+                <Version>9.9.9.999</Version>
                 <IsVersionFromRSM>True</IsVersionFromRSM>
                 <IsVMEnabledForRSMUpgrades>True</IsVMEnabledForRSMUpgrades>
                 <Uris>
diff --git a/tests/ga/test_agent_update_handler.py b/tests/ga/test_agent_update_handler.py
index 4e079ab7..682c76da 100644
--- a/tests/ga/test_agent_update_handler.py
+++ b/tests/ga/test_agent_update_handler.py
@@ -18,7 +18,7 @@ from tests.ga.test_update import UpdateTestCase
 from tests.lib.http_request_predicates import HttpRequestPredicates
 from tests.lib.mock_wire_protocol import mock_wire_protocol, MockHttpResponse
 from tests.lib.wire_protocol_data import DATA_FILE
-from tests.lib.tools import clear_singleton_instances, load_bin_data, patch
+from tests.lib.tools import clear_singleton_instances, load_bin_data, patch, skip_if_predicate_true
 
 
 class TestAgentUpdate(UpdateTestCase):
@@ -277,6 +277,24 @@ class TestAgentUpdate(UpdateTestCase):
             self._assert_agent_directories_exist_and_others_dont_exist(versions=["9.9.9.10", str(CURRENT_VERSION)])
             self._assert_agent_exit_process_telemetry_emitted(ustr(context.exception.reason))
 
+    def test_it_should_not_allow_rsm_downgrade_if_rsm_version_is_available_less_than_current_version(self):
+        data_file = DATA_FILE.copy()
+        data_file["ext_conf"] = "wire/ext_conf_rsm_version.xml"
+
+        # Set the test environment by adding 20 random agents to the agent directory
+        self.prepare_agents()
+        self.assertEqual(20, self.agent_count(), "Agent directories not set properly")
+
+        downgraded_version = "2.5.0"
+
+        with self._get_agent_update_handler(test_data=data_file) as (agent_update_handler, _):
+            agent_update_handler._protocol.mock_wire_data.set_version_in_agent_family(downgraded_version)
+            agent_update_handler._protocol.mock_wire_data.set_incarnation(2)
+            agent_update_handler._protocol.client.update_goal_state()
+            agent_update_handler.run(agent_update_handler._protocol.get_goal_state(), True)
+            self.assertFalse(os.path.exists(self.agent_dir(downgraded_version)),"New agent directory should not be found")
+
+    @skip_if_predicate_true(lambda: True, "Enable this test when rsm downgrade scenario fixed")
     def test_it_should_downgrade_agent_if_rsm_version_is_available_less_than_current_version(self):
         data_file = DATA_FILE.copy()
         data_file["ext_conf"] = "wire/ext_conf_rsm_version.xml"
@@ -302,7 +320,7 @@ class TestAgentUpdate(UpdateTestCase):
         self.prepare_agents(count=1)
         data_file = DATA_FILE.copy()
         data_file["ext_conf"] = "wire/ext_conf_rsm_version.xml"
-        version = "5.2.0.1"
+        version = "9.9.9.999"
         with self._get_agent_update_handler(test_data=data_file, autoupdate_frequency=10) as (agent_update_handler, mock_telemetry):
             agent_update_handler._protocol.mock_wire_data.set_version_in_agent_family(version)
             agent_update_handler._protocol.mock_wire_data.set_incarnation(2)
@@ -370,7 +388,7 @@ class TestAgentUpdate(UpdateTestCase):
         self.prepare_agents()
         self.assertEqual(20, self.agent_count(), "Agent directories not set properly")
 
-        version = "5.2.0.4"
+        version = "9.9.9.999"
 
         with self._get_agent_update_handler(test_data=data_file) as (agent_update_handler, mock_telemetry):
             agent_update_handler._protocol.mock_wire_data.set_version_in_agent_family(version)
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index defc3e6c..f810dda8 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -1154,13 +1154,13 @@ class TestUpdate(UpdateTestCase):
 
                         # Case 3: rsm version in GS != Current Version; update fail and report error
                         protocol.mock_wire_data.set_extension_config("wire/ext_conf_rsm_version.xml")
-                        protocol.mock_wire_data.set_version_in_agent_family("5.2.0.1")
+                        protocol.mock_wire_data.set_version_in_agent_family("9.9.9.999")
                         update_goal_state_and_run_handler()
                         self.assertTrue("updateStatus" in protocol.aggregate_status['aggregateStatus']['guestAgentStatus'],
                                         "updateStatus should be in status blob. Warns: {0}".format(patch_warn.call_args_list))
                         update_status = protocol.aggregate_status['aggregateStatus']['guestAgentStatus']["updateStatus"]
                         self.assertEqual(VMAgentUpdateStatuses.Error, update_status['status'], "Status should be an error")
-                        self.assertEqual(update_status['expectedVersion'], "5.2.0.1", "incorrect version reported")
+                        self.assertEqual(update_status['expectedVersion'], "9.9.9.999", "incorrect version reported")
                         self.assertEqual(update_status['code'], 1, "incorrect code reported")
 
     def test_it_should_wait_to_fetch_first_goal_state(self):
@@ -1456,7 +1456,7 @@ class TestAgentUpgrade(UpdateTestCase):
         with self.__get_update_handler(iterations=no_of_iterations, test_data=data_file,
                                        autoupdate_frequency=test_frequency) as (update_handler, _):
             # Given version which will fail on first attempt, then rsm shouldn't make any futher attempts since GS is not updated
-            update_handler._protocol.mock_wire_data.set_version_in_agent_family("5.2.1.0")
+            update_handler._protocol.mock_wire_data.set_version_in_agent_family("9.9.9.999")
             update_handler._protocol.mock_wire_data.set_incarnation(2)
             update_handler.run(debug=True)
 
@@ -1526,12 +1526,12 @@ class TestAgentUpgrade(UpdateTestCase):
                       kwarg['op'] in (WALAEventOperation.AgentUpgrade, WALAEventOperation.Download)]
         # This will throw if corresponding message not found so not asserting on that
         rsm_version_found = next(kwarg for kwarg in agent_msgs if
-                                       "New agent version:5.2.1.0 requested by RSM in Goal state incarnation_1, will update the agent before processing the goal state" in kwarg['message'])
+                                       "New agent version:9.9.9.999 requested by RSM in Goal state incarnation_1, will update the agent before processing the goal state" in kwarg['message'])
         self.assertTrue(rsm_version_found['is_success'],
                         "The rsm version found op should be reported as a success")
 
         skipping_update = next(kwarg for kwarg in agent_msgs if
-                               "No matching package found in the agent manifest for version: 5.2.1.0 in goal state incarnation: incarnation_1, skipping agent update" in kwarg['message'])
+                               "No matching package found in the agent manifest for version: 9.9.9.999 in goal state incarnation: incarnation_1, skipping agent update" in kwarg['message'])
         self.assertEqual(skipping_update['version'], str(CURRENT_VERSION),
                          "The not found message should be reported from current agent version")
         self.assertFalse(skipping_update['is_success'], "The not found op should be reported as a failure")
@@ -1752,6 +1752,7 @@ class TestAgentUpgrade(UpdateTestCase):
             self.__assert_exit_code_successful(update_handler)
             self.__assert_upgrade_telemetry_emitted(mock_telemetry, version="9.9.9.10")
 
+    @skip_if_predicate_true(lambda: True, "Enable this test when rsm downgrade scenario fixed")
     def test_it_should_mark_current_agent_as_bad_version_on_downgrade(self):
         # Create Agent directory for current agent
         self.prepare_agents(count=1)
diff --git a/tests_e2e/tests/agent_publish/rollback.py b/tests_e2e/tests/agent_publish/rollback.py
index fb4e30d3..bce99d11 100644
--- a/tests_e2e/tests/agent_publish/rollback.py
+++ b/tests_e2e/tests/agent_publish/rollback.py
@@ -46,7 +46,7 @@ class Rollback(AgentVmTest):
         self._check_rsm_gs(rollback_version)
 
         output: str = self._ssh_client.run_command(
-            "update-waagent-conf Debug.EnableGAVersioning=y", use_sudo=True)
+            "update-waagent-conf Debug.EnableGAVersioning=y Debug.EnableRsmDowngrade=y", use_sudo=True)
         log.info('Successfully enabled rsm updates \n %s', output)
 
         verify_current_agent_version(self._ssh_client, rollback_version)
diff --git a/tests_e2e/tests/agent_update/rsm_update.py b/tests_e2e/tests/agent_update/rsm_update.py
index ad2222d1..011aa9fa 100644
--- a/tests_e2e/tests/agent_update/rsm_update.py
+++ b/tests_e2e/tests/agent_update/rsm_update.py
@@ -138,7 +138,7 @@ class RsmUpdateBvt(AgentVmTest):
         log.info(
             'Executing update-waagent-conf remote script to update agent update config flags to allow and download test versions')
         output: str = self._ssh_client.run_command(
-                              "update-waagent-conf AutoUpdate.UpdateToLatestVersion=y Debug.EnableGAVersioning=y AutoUpdate.GAFamily=Test", use_sudo=True)
+                              "update-waagent-conf AutoUpdate.UpdateToLatestVersion=y Debug.EnableGAVersioning=y Debug.EnableRsmDowngrade=y AutoUpdate.GAFamily=Test", use_sudo=True)
         log.info('Successfully updated agent update config \n %s', output)
 
     def _verify_guest_agent_update(self, requested_version: str) -> None: