Repository: Azure/WALinuxAgent
Pull request #2275 (2021-06-17 01:53:22)

Refactoring of Agent's main loop
Some refactoring of the agent's main loop to facilitate the implementation of FastTrack.

* Split the code of the main loop across several helper functions
* Moved the check for new goal state (and some other helper logic) from ExtHandlersHandler.run to UpdateHandler.run (main loop)
* Separated the calls to ExtHandlersHandler.run and ExtHandlersHandler.report_ext_handlers_status



narrieta: FYI: @jscalev 
codecov[bot]: # [Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) Report
> Merging [#2275](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (1fb06bc) into [develop](https://codecov.io/gh/Azure/WALinuxAgent/commit/9da99cc6e19c54c6b3a4afc48bcdd1cdfac183e4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (9da99cc) will **decrease** coverage by `0.14%`.
> The diff coverage is `78.88%`.

> :exclamation: Current head 1fb06bc differs from pull request most recent head a374db3. Consider uploading reports for the commit a374db3 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275/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/2275?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)

```diff
@@             Coverage Diff             @@
##           develop    #2275      +/-   ##
===========================================
- Coverage    70.80%   70.65%   -0.15%     
===========================================
  Files           96       96              
  Lines        13857    13866       +9     
  Branches      1979     1980       +1     
===========================================
- Hits          9811     9797      -14     
- Misses        3618     3640      +22     
- Partials       428      429       +1     
```


| [Impacted Files](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275?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/2275/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2dhL3VwZGF0ZS5weQ==) | `87.17% <63.63%> (-1.50%)` | :arrow_down: |
| [azurelinuxagent/ga/exthandlers.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2dhL2V4dGhhbmRsZXJzLnB5) | `86.14% <93.33%> (-0.66%)` | :arrow_down: |
| [azurelinuxagent/common/exception.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2NvbW1vbi9leGNlcHRpb24ucHk=) | `98.94% <100.00%> (+0.01%)` | :arrow_up: |
| [azurelinuxagent/common/event.py](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2NvbW1vbi9ldmVudC5weQ==) | `85.26% <0.00%> (-0.69%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275?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/2275?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure). Last update [9da99cc...a374db3](https://codecov.io/gh/Azure/WALinuxAgent/pull/2275?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/common/event.py b/azurelinuxagent/common/event.py
index 0cb654b8..4f94ec48 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -105,7 +105,6 @@ class WALAEventOperation:
     PersistFirewallRules = "PersistFirewallRules"
     PluginSettingsVersionMismatch = "PluginSettingsVersionMismatch"
     InvalidExtensionConfig = "InvalidExtensionConfig"
-    ProcessGoalState = "ProcessGoalState"
     Provision = "Provision"
     ProvisionGuestAgent = "ProvisionGuestAgent"
     RemoteAccessHandling = "RemoteAccessHandling"
diff --git a/azurelinuxagent/common/exception.py b/azurelinuxagent/common/exception.py
index aa3737ea..c0421d00 100644
--- a/azurelinuxagent/common/exception.py
+++ b/azurelinuxagent/common/exception.py
@@ -22,6 +22,15 @@ Defines all exceptions
 """
 
 
+class ExitException(BaseException):
+    """
+    Used to exit the agent's process
+    """
+    def __init__(self, reason):
+        super(ExitException, self).__init__()
+        self.reason = reason
+
+
 class AgentError(Exception):
     """
     Base class of agent error.
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index 1003def1..5a44d5d9 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -276,9 +276,6 @@ class ExtHandlersHandler(object):
     def __init__(self, protocol):
         self.protocol = protocol
         self.ext_handlers = None
-        self.last_etag = None
-        self.log_report = False
-        self.log_process = False
         # The GoalState Aggregate status needs to report the last status of the GoalState. Since we only process
         # extensions on incarnation change, we need to maintain its state.
         # Setting the status to None here. This would be overridden as soon as the first GoalState is processed
@@ -287,10 +284,6 @@ class ExtHandlersHandler(object):
 
         self.report_status_error_state = ErrorState()
 
-    def _incarnation_changed(self, etag):
-        # Skip processing if GoalState incarnation did not change
-        return self.last_etag != etag
-
     def __last_gs_unsupported(self):
 
         # Return if the last GoalState was unsupported
@@ -329,37 +322,48 @@ class ExtHandlersHandler(object):
         return activity_id, correlation_id, gs_creation_time
 
     def run(self):
+        etag, activity_id, correlation_id, gs_creation_time = None, None, None, None
 
         try:
+            # self.ext_handlers needs to be initialized first, since status reporting depends on it
             self.ext_handlers, etag = self.protocol.get_ext_handlers()
-            msg = u"Handle extensions updates for incarnation {0}".format(etag)
-            logger.verbose(msg)
-            # Log status report success on new config
-            self.log_report = True
-            incarnation_changed = self._incarnation_changed(etag)
-            if self._extension_processing_allowed() and incarnation_changed:
-                activity_id, correlation_id, gs_creation_time = self.get_goal_state_debug_metadata()
-
-                logger.info(
-                    "ProcessGoalState started [Incarnation: {0}; Activity Id: {1}; Correlation Id: {2}; GS Creation Time: {3}]".format(
-                        etag, activity_id, correlation_id, gs_creation_time))
-
-                self.__process_and_handle_extensions(etag)
-                self.last_etag = etag
-
-            self.report_ext_handlers_status(incarnation_changed=incarnation_changed)
-            self._cleanup_outdated_handlers()
+
+            if not self._extension_processing_allowed():
+                return
+
+            activity_id, correlation_id, gs_creation_time = self.get_goal_state_debug_metadata()
         except Exception as error:
-            msg = u"Exception processing extension handlers: {0}".format(ustr(error))
-            detailed_msg = '{0} {1}'.format(msg, traceback.extract_tb(get_traceback(error)))
+            msg = u"ProcessExtensionsInGoalState - Exception processing extension handlers: {0}\n{1}".format(ustr(error), traceback.extract_tb(get_traceback(error)))
             logger.warn(msg)
-            add_event(AGENT_NAME,
-                      version=CURRENT_VERSION,
-                      op=WALAEventOperation.ExtensionProcessing,
-                      is_success=False,
-                      message=detailed_msg)
+            add_event(op=WALAEventOperation.ExtensionProcessing, is_success=False, message=msg, log_event=False)
             return
 
+        def goal_state_debug_info(duration=None):
+            if duration is None:
+                return "[Incarnation: {0}; Activity Id: {1}; Correlation Id: {2}; GS Creation Time: {3}]".format(etag, activity_id, correlation_id, gs_creation_time)
+            else:
+                return "[Incarnation: {0}; {1} ms; Activity Id: {2}; Correlation Id: {3}; GS Creation Time: {4}]".format(etag, duration, activity_id, correlation_id, gs_creation_time)
+
+        utc_start = datetime.datetime.utcnow()
+        error = None
+        message = "ProcessExtensionsInGoalState started {0}".format(goal_state_debug_info())
+        logger.info(message)
+        add_event(op=WALAEventOperation.ExtensionProcessing, message=message)
+        try:
+            self.__process_and_handle_extensions(etag)
+            self._cleanup_outdated_handlers()
+        except Exception as error:
+            error = u"ProcessExtensionsInGoalState - Exception processing extension handlers: {0}\n{1}".format(ustr(error), traceback.extract_tb(get_traceback(error)))
+        finally:
+            duration = elapsed_milliseconds(utc_start)
+            if error is None:
+                message = 'ProcessExtensionsInGoalState completed {0}'.format(goal_state_debug_info(duration=duration))
+                logger.info(message)
+            else:
+                message = 'ProcessExtensionsInGoalState failed {0}\nError:{1}'.format(goal_state_debug_info(duration=duration), error)
+                logger.warn(message)
+            add_event(op=WALAEventOperation.ExtensionProcessing, is_success=(error is None), message=message, log_event=False, duration=duration)
+
     def __get_unsupported_features(self):
         required_features = self.protocol.get_required_features()
         supported_features = get_agent_supported_features_list_for_crp()
@@ -701,7 +705,6 @@ class ExtHandlersHandler(object):
              1- Ensure the handler is installed
              2- Check if extension is enabled or disabled and then process accordingly
         """
-        self.log_process = True
         uninstall_exit_code = None
         old_ext_handler_i = ext_handler_i.get_installed_ext_handler()
 
@@ -831,7 +834,6 @@ class ExtHandlersHandler(object):
             Disable is a legacy behavior, CRP doesn't support it, its only for XML based extensions.
             In case we get a disable request, just disable that extension.
         """
-        self.log_process = True
         handler_state = ext_handler_i.get_handler_state()
         ext_handler_i.logger.info("[Disable] current handler state is: {0}", handler_state.lower())
         if handler_state == ExtHandlerState.Enabled:
@@ -846,7 +848,6 @@ class ExtHandlersHandler(object):
                 CRP will only set the HandlerState to Uninstall if all its extensions are set to be disabled)
             2- Finally uninstall the handler
         """
-        self.log_process = True
         handler_state = ext_handler_i.get_handler_state()
         ext_handler_i.logger.info("[Uninstall] current handler state is: {0}", handler_state.lower())
         if handler_state != ExtHandlerState.NotInstalled:
@@ -917,57 +918,66 @@ class ExtHandlersHandler(object):
         """
         Go through handler_state dir, collect and report status
         """
-        vm_status = VMStatus(status="Ready", message="Guest Agent is running",
-                             gs_aggregate_status=self.__gs_aggregate_status)
+        try:
+            vm_status = VMStatus(status="Ready", message="Guest Agent is running",
+                                 gs_aggregate_status=self.__gs_aggregate_status)
 
-        handlers_to_report = []
+            handlers_to_report = []
 
-        # Incase of Unsupported error, report the status of the handlers in the VM
-        if self.__last_gs_unsupported():
-            handlers_to_report = self.__get_handlers_on_file_system(incarnation_changed)
+            # In case of Unsupported error, report the status of the handlers in the VM
+            if self.__last_gs_unsupported():
+                handlers_to_report = self.__get_handlers_on_file_system(incarnation_changed)
 
-        # If GoalState supported, report the status of extension handlers that were requested by the GoalState
-        elif not self.__last_gs_unsupported() and self.ext_handlers is not None:
-            handlers_to_report = self.ext_handlers.extHandlers
+            # If GoalState supported, report the status of extension handlers that were requested by the GoalState
+            elif not self.__last_gs_unsupported() and self.ext_handlers is not None:
+                handlers_to_report = self.ext_handlers.extHandlers
 
-        for ext_handler in handlers_to_report:
+            for ext_handler in handlers_to_report:
+                try:
+                    self.report_ext_handler_status(vm_status, ext_handler, incarnation_changed)
+                except ExtensionError as error:
+                    add_event(op=WALAEventOperation.ExtensionProcessing, is_success=False, message=ustr(error))
+
+            logger.verbose("Report vm agent status")
             try:
-                self.report_ext_handler_status(vm_status, ext_handler, incarnation_changed)
-            except ExtensionError as error:
-                add_event(op=WALAEventOperation.ExtensionProcessing, is_success=False, message=ustr(error))
+                self.protocol.report_vm_status(vm_status)
+                logger.verbose("Completed vm agent status report successfully")
+                self.report_status_error_state.reset()
+            except ProtocolNotFoundError as error:
+                self.report_status_error_state.incr()
+                message = "Failed to report vm agent status: {0}".format(error)
+                logger.verbose(message)
+            except ProtocolError as error:
+                self.report_status_error_state.incr()
+                message = "Failed to report vm agent status: {0}".format(error)
+                add_event(AGENT_NAME,
+                          version=CURRENT_VERSION,
+                          op=WALAEventOperation.ExtensionProcessing,
+                          is_success=False,
+                          message=message)
 
-        logger.verbose("Report vm agent status")
-        try:
-            self.protocol.report_vm_status(vm_status)
-            if self.log_report:
-                logger.verbose("Completed vm agent status report")
-            self.report_status_error_state.reset()
-        except ProtocolNotFoundError as error:
-            self.report_status_error_state.incr()
-            message = "Failed to report vm agent status: {0}".format(error)
-            logger.verbose(message)
-        except ProtocolError as error:
-            self.report_status_error_state.incr()
-            message = "Failed to report vm agent status: {0}".format(error)
-            add_event(AGENT_NAME,
-                      version=CURRENT_VERSION,
-                      op=WALAEventOperation.ExtensionProcessing,
-                      is_success=False,
-                      message=message)
+            if self.report_status_error_state.is_triggered():
+                message = "Failed to report vm agent status for more than {0}" \
+                    .format(self.report_status_error_state.min_timedelta)
 
-        if self.report_status_error_state.is_triggered():
-            message = "Failed to report vm agent status for more than {0}" \
-                .format(self.report_status_error_state.min_timedelta)
+                add_event(AGENT_NAME,
+                          version=CURRENT_VERSION,
+                          op=WALAEventOperation.ReportStatusExtended,
+                          is_success=False,
+                          message=message)
+
+                self.report_status_error_state.reset()
 
+            self.write_ext_handlers_status_to_info_file(vm_status)
+
+        except Exception as error:
+            msg = u"Failed to report status: {0}\n{1}".format(ustr(error), traceback.extract_tb(get_traceback(error)))
+            logger.warn(msg)
             add_event(AGENT_NAME,
                       version=CURRENT_VERSION,
-                      op=WALAEventOperation.ReportStatusExtended,
+                      op=WALAEventOperation.ReportStatus,
                       is_success=False,
-                      message=message)
-
-            self.report_status_error_state.reset()
-
-        self.write_ext_handlers_status_to_info_file(vm_status)
+                      message=msg)
 
     @staticmethod
     def write_ext_handlers_status_to_info_file(vm_status):
diff --git a/azurelinuxagent/ga/remoteaccess.py b/azurelinuxagent/ga/remoteaccess.py
index f18f46e2..f12fd0d4 100644
--- a/azurelinuxagent/ga/remoteaccess.py
+++ b/azurelinuxagent/ga/remoteaccess.py
@@ -48,18 +48,14 @@ class RemoteAccessHandler(object):
         self._protocol = protocol
         self._cryptUtil = CryptUtil(conf.get_openssl_cmd())
         self._remote_access = None
-        self._incarnation = 0
         self._check_existing_jit_users = True
 
     def run(self):
         try:
             if self._os_util.jit_enabled:
-                current_incarnation = self._protocol.get_incarnation()
-                if self._incarnation != current_incarnation:
-                    # something changed. Handle remote access if any.
-                    self._incarnation = current_incarnation
-                    self._remote_access = self._protocol.client.get_remote_access()
-                    self._handle_remote_access()
+                # Handle remote access if any.
+                self._remote_access = self._protocol.client.get_remote_access()
+                self._handle_remote_access()
         except Exception as e:
             msg = u"Exception processing goal state for remote access users: {0} {1}".format(ustr(e), traceback.format_exc())
             add_event(AGENT_NAME,
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 3ff2bf18..c656388d 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -43,8 +43,8 @@ from azurelinuxagent.common.persist_firewall_rules import PersistFirewallRulesHa
 from azurelinuxagent.common.cgroupconfigurator import CGroupConfigurator
 
 from azurelinuxagent.common.event import add_event, initialize_event_logger_vminfo_common_parameters, \
-    elapsed_milliseconds, WALAEventOperation, EVENTS_DIRECTORY
-from azurelinuxagent.common.exception import ResourceGoneError, UpdateError
+    WALAEventOperation, EVENTS_DIRECTORY
+from azurelinuxagent.common.exception import ResourceGoneError, UpdateError, ExitException
 from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.osutil import get_osutil, systemd
 from azurelinuxagent.common.protocol.util import get_protocol_util
@@ -100,7 +100,7 @@ class UpdateHandler(object):
         self.osutil = get_osutil()
         self.protocol_util = get_protocol_util()
 
-        self.running = True
+        self._is_running = True
         self.last_attempt_time = None
 
         self.agents = []
@@ -117,6 +117,8 @@ class UpdateHandler(object):
         self._heartbeat_counter = 0
         self._heartbeat_update_goal_state_error_count = 0
 
+        self.last_incarnation = None
+
     def run_latest(self, child_args=None):
         """
         This method is called from the daemon to find and launch the most
@@ -225,7 +227,7 @@ class UpdateHandler(object):
 
         except Exception as e:
             # Ignore child errors during termination
-            if self.running:
+            if self.is_running:
                 msg = u"Agent {0} launched with command '{1}' failed with exception: {2}".format(
                     agent_name,
                     agent_cmd,
@@ -317,64 +319,15 @@ class UpdateHandler(object):
 
             goal_state_interval = conf.get_goal_state_period() if conf.get_extensions_enabled() else GOAL_STATE_INTERVAL_DISABLED
 
-            while self.running:
-                #
-                # Check that the parent process (the agent's daemon) is still running
-                #
-                if not debug and self._is_orphaned:
-                    logger.info("Agent {0} is an orphan -- exiting", CURRENT_AGENT)
-                    break
-
-                #
-                # Check that all the threads are still running
-                #
-                for thread_handler in all_thread_handlers:
-                    if not thread_handler.is_alive():
-                        logger.warn("{0} thread died, restarting".format(thread_handler.get_thread_name()))
-                        thread_handler.start()
-
-                #
-                # Process the goal state
-                #
-                if not protocol.try_update_goal_state():
-                    self._heartbeat_update_goal_state_error_count += 1
-                else:
-                    if self._upgrade_available(protocol):
-                        available_agent = self.get_latest_agent()
-                        if available_agent is None:
-                            logger.info(
-                                "Agent {0} is reverting to the installed agent -- exiting",
-                                CURRENT_AGENT)
-                        else:
-                            logger.info(
-                                u"Agent {0} discovered update {1} -- exiting",
-                                CURRENT_AGENT,
-                                available_agent.name)
-                        break
-
-                    utc_start = datetime.utcnow()
-
-                    last_etag = exthandlers_handler.last_etag
-                    exthandlers_handler.run()
-
-                    remote_access_handler.run()
-
-                    if last_etag != exthandlers_handler.last_etag:
-                        self._ensure_readonly_files()
-                        duration = elapsed_milliseconds(utc_start)
-                        activity_id, correlation_id, gs_creation_time = exthandlers_handler.get_goal_state_debug_metadata()
-                        msg = 'ProcessGoalState completed [Incarnation: {0}; {1} ms; Activity Id: {2}; Correlation Id: {3}; GS Creation Time: {4}]'.format(
-                            exthandlers_handler.last_etag, duration, activity_id, correlation_id, gs_creation_time)
-                        logger.info(msg)
-                        add_event(
-                            AGENT_NAME,
-                            op=WALAEventOperation.ProcessGoalState,
-                            duration=duration,
-                            message=msg)
-
+            while self.is_running:
+                self._check_daemon_running(debug)
+                self._check_threads_running(all_thread_handlers)
+                self._process_goal_state(protocol, exthandlers_handler, remote_access_handler)
                 self._send_heartbeat_telemetry(protocol)
                 time.sleep(goal_state_interval)
 
+        except ExitException as exitException:
+            logger.info(exitException.reason)
         except Exception as error:
             msg = u"Agent {0} failed with exception: {1}".format(CURRENT_AGENT, ustr(error))
             self._set_sentinel(msg=msg)
@@ -387,6 +340,46 @@ class UpdateHandler(object):
         self._shutdown()
         sys.exit(0)
 
+    def _check_daemon_running(self, debug):
+        # Check that the parent process (the agent's daemon) is still running
+        if not debug and self._is_orphaned:
+            raise ExitException("Agent {0} is an orphan -- exiting".format(CURRENT_AGENT))
+
+    def _check_threads_running(self, all_thread_handlers):
+        # Check that all the threads are still running
+        for thread_handler in all_thread_handlers:
+            if not thread_handler.is_alive():
+                logger.warn("{0} thread died, restarting".format(thread_handler.get_thread_name()))
+                thread_handler.start()
+
+    def _process_goal_state(self, protocol, exthandlers_handler, remote_access_handler):
+        if not protocol.try_update_goal_state():
+            self._heartbeat_update_goal_state_error_count += 1
+            return
+
+        if self._upgrade_available(protocol):
+            available_agent = self.get_latest_agent()
+            if available_agent is None:
+                reason = "Agent {0} is reverting to the installed agent -- exiting".format(CURRENT_AGENT)
+            else:
+                reason = "Agent {0} discovered update {1} -- exiting".format(CURRENT_AGENT, available_agent.name)
+            raise ExitException(reason)
+
+        incarnation = protocol.get_incarnation()
+
+        try:
+            if incarnation != self.last_incarnation:
+                exthandlers_handler.run()
+
+            # report status always, even if the goal state did not change
+            # do it before processing the remote access, since that operation can take a long time
+            exthandlers_handler.report_ext_handlers_status(incarnation_changed=incarnation != self.last_incarnation)
+
+            if incarnation != self.last_incarnation:
+                remote_access_handler.run()
+        finally:
+            self.last_incarnation = incarnation
+
     def forward_signal(self, signum, frame):
         if signum == signal.SIGTERM:
             self._shutdown()
@@ -586,6 +579,14 @@ class UpdateHandler(object):
         pid_files.sort(key=lambda f: int(pid_re.match(os.path.basename(f)).group(1)))
         return pid_files
 
+    @property
+    def is_running(self):
+        return self._is_running
+
+    @is_running.setter
+    def is_running(self, value):
+        self._is_running = value
+
     @property
     def _is_clean_start(self):
         return not os.path.isfile(self._sentinel_file_path())
@@ -678,7 +679,7 @@ class UpdateHandler(object):
     def _shutdown(self):
         # Todo: Ensure all threads stopped when shutting down the main extension handler to ensure that the state of
         # all threads is clean.
-        self.running = False
+        self.is_running = False
 
         if not os.path.isfile(self._sentinel_file_path()):
             return


Test patch:
diff --git a/tests/ga/test_extension.py b/tests/ga/test_extension.py
index e72c93a9..4a399bf4 100644
--- a/tests/ga/test_extension.py
+++ b/tests/ga/test_extension.py
@@ -25,7 +25,6 @@ import subprocess
 import tempfile
 import time
 import unittest
-import uuid
 
 from azurelinuxagent.common import conf
 from azurelinuxagent.common.agent_supported_feature import get_agent_supported_features_list_for_crp, \
@@ -136,6 +135,8 @@ class TestExtensionCleanup(AgentTestCase):
     def test_cleanup_leaves_installed_extensions(self):
         with self._setup_test_env(mockwiredata.DATA_FILE_MULTIPLE_EXT) as (exthandlers_handler, protocol, no_of_exts):
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(no_of_exts, TestExtensionCleanup._count_packages(),
                              "No of extensions in config doesn't match the packages")
             self.assertEqual(no_of_exts, TestExtensionCleanup._count_extension_directories(),
@@ -146,6 +147,7 @@ class TestExtensionCleanup(AgentTestCase):
     def test_cleanup_removes_uninstalled_extensions(self):
         with self._setup_test_env(mockwiredata.DATA_FILE_MULTIPLE_EXT) as (exthandlers_handler, protocol, no_of_exts):
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertEqual(no_of_exts, TestExtensionCleanup._count_packages(),
                              "No of extensions in config doesn't match the packages")
             self._assert_ext_handler_status(protocol.aggregate_status, "Ready", expected_ext_handler_count=no_of_exts,
@@ -157,6 +159,7 @@ class TestExtensionCleanup(AgentTestCase):
 
             protocol.client.update_goal_state()
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             self.assertEqual(0, TestExtensionCleanup._count_packages(), "All packages must be deleted")
             self._assert_ext_handler_status(protocol.aggregate_status, "Ready", expected_ext_handler_count=0,
@@ -178,6 +181,8 @@ class TestExtensionCleanup(AgentTestCase):
             self.assertEqual(no_of_orphaned_packages, TestExtensionCleanup._count_extension_directories(),
                              "Test Setup error - Not enough extension directories")
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(no_of_exts, TestExtensionCleanup._count_extension_directories(),
                              "There should be no extension directories in FS")
             self.assertIsNone(protocol.aggregate_status,
@@ -192,6 +197,8 @@ class TestExtensionCleanup(AgentTestCase):
         with self._setup_test_env(mockwiredata.DATA_FILE_EXT_SINGLE) as (exthandlers_handler, protocol, no_of_exts):
             with patch("azurelinuxagent.common.cgroupapi.subprocess.Popen", mock_fail_popen):
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 self._assert_ext_handler_status(protocol.aggregate_status, "NotReady",
                                                 expected_ext_handler_count=no_of_exts,
                                                 version="1.0.0", verify_ext_reported=False)
@@ -204,6 +211,7 @@ class TestExtensionCleanup(AgentTestCase):
 
             protocol.client.update_goal_state()
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             self.assertEqual(0, TestExtensionCleanup._count_packages(), "All packages must be deleted")
             self.assertEqual(0, TestExtensionCleanup._count_extension_directories(),
@@ -227,6 +235,7 @@ class TestExtensionCleanup(AgentTestCase):
         with self._setup_test_env(mockwiredata.DATA_FILE_MULTIPLE_EXT) as (exthandlers_handler, protocol, orig_no_of_exts):
             # Run 1 - GS has no required features and contains 5 extensions
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertEqual(orig_no_of_exts, TestExtensionCleanup._count_packages(),
                              "No of extensions in config doesn't match the packages")
             self.assertEqual(orig_no_of_exts, TestExtensionCleanup._count_extension_directories(),
@@ -244,6 +253,7 @@ class TestExtensionCleanup(AgentTestCase):
             protocol.mock_wire_data.set_extensions_config_sequence_number(random.randint(10, 100))
             protocol.client.update_goal_state()
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertGreater(orig_no_of_exts, 1, "No of extensions to check should be > 1")
             self.assertEqual(orig_no_of_exts, TestExtensionCleanup._count_packages(),
                              "No of extensions should not be changed")
@@ -269,6 +279,7 @@ class TestExtensionCleanup(AgentTestCase):
             protocol.mock_wire_data.set_extensions_config_sequence_number(extension_seq_no)
             protocol.client.update_goal_state()
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertEqual(1, TestExtensionCleanup._count_packages(),
                              "No of extensions should not be changed")
             self.assertEqual(1, TestExtensionCleanup._count_extension_directories(),
@@ -490,6 +501,7 @@ class TestExtension(AgentTestCase):
 
         # Ensure initial install and enable is successful
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(0, patch_command.call_count)
         self._assert_handler_status(protocol.report_vm_status, "Ready", expected_ext_count=1, version="1.0.0")
@@ -517,12 +529,14 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
 
         # Test goal state not changed
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
@@ -532,6 +546,8 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 1)
@@ -543,6 +559,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
         self._assert_ext_status(protocol.report_vm_status, "success", 2)
@@ -554,6 +571,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.2.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 3)
@@ -564,6 +582,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "NotReady", 1, "1.2.0")
 
@@ -573,6 +592,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_no_handler_status(protocol.report_vm_status)
 
@@ -581,6 +601,8 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_no_handler_status(protocol.report_vm_status)
 
     def test_it_should_only_download_extension_manifest_once_per_goal_state(self, *args):
@@ -594,25 +616,17 @@ class TestExtension(AgentTestCase):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
         _assert_handler_status_and_manifest_download_count(protocol, test_data, 1)
 
-        for _ in range(5):
-            exthandlers_handler.run()
-            # The extension manifest should only be downloaded once as incarnation did not change
-            _assert_handler_status_and_manifest_download_count(protocol, test_data, 1)
-
         # Update Incarnation
         test_data.set_incarnation(2)
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
         _assert_handler_status_and_manifest_download_count(protocol, test_data, 2)
 
-        for _ in range(5):
-            exthandlers_handler.run()
-            # The extension manifest should be downloaded twice now as incarnation changed once
-            _assert_handler_status_and_manifest_download_count(protocol, test_data, 2)
-
     def test_it_should_fail_handler_on_bad_extension_config_and_report_error(self, mock_get, mock_crypt_util, *args):
 
         invalid_config_dir = os.path.join(data_dir, "wire", "invalid_config")
@@ -626,6 +640,7 @@ class TestExtension(AgentTestCase):
 
             with patch('azurelinuxagent.ga.exthandlers.add_event') as patch_add_event:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self._assert_handler_status(protocol.report_vm_status, "NotReady", 0, "1.0.0")
 
                 invalid_config_errors = [kw for _, kw in patch_add_event.call_args_list if
@@ -641,6 +656,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt_util, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
         self.assertTrue(protocol.report_vm_status.called)
         args, _ = protocol.report_vm_status.call_args
         vm_status = args[0]
@@ -664,6 +680,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -676,6 +693,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 1)
@@ -689,6 +707,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.2.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 2)
@@ -702,6 +721,7 @@ class TestExtension(AgentTestCase):
         extension_version = "1.0.0"
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, extension_version)
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -713,6 +733,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_no_handler_status(protocol.report_vm_status)
         self._assert_ext_pkg_file_status(expected_to_be_present=False, extension_version=extension_version)
@@ -723,6 +744,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -735,6 +757,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 1)
@@ -748,6 +771,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.2.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 2)
@@ -760,6 +784,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_no_handler_status(protocol.report_vm_status)
         self._assert_ext_pkg_file_status(expected_to_be_present=False, extension_version="1.2.0")
@@ -769,6 +794,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt_util, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         expected_ext_handlers = ["OSTCExtensions.ExampleHandlerLinux", "Microsoft.Powershell.ExampleExtension",
                                  "Microsoft.EnterpriseCloud.Monitoring.ExampleHandlerLinux",
@@ -799,6 +825,7 @@ class TestExtension(AgentTestCase):
         test_ext = extension_emulator(name="OSTCExtensions.ExampleHandlerLinux")
         with enable_invocations(test_ext) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self._assert_handler_status(protocol.report_vm_status, "Ready", 0, "1.0.0")
             invocation_record.compare(
                 (test_ext, ExtensionCommandNames.INSTALL),
@@ -812,6 +839,7 @@ class TestExtension(AgentTestCase):
 
         with enable_invocations(test_ext) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertTrue(protocol.report_vm_status.called)
             args, _ = protocol.report_vm_status.call_args
             self.assertEqual(0, len(args[0].vmAgent.extensionHandlers))
@@ -825,6 +853,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
     def test_ext_handler_no_ext(self, *args):
@@ -833,6 +862,7 @@ class TestExtension(AgentTestCase):
 
         # Assert no extension handler status
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
         self._assert_no_handler_status(protocol.report_vm_status)
 
     def test_ext_handler_sequencing(self, *args):
@@ -845,6 +875,7 @@ class TestExtension(AgentTestCase):
 
         with enable_invocations(dep_ext_level_2, dep_ext_level_1) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0",
                                         expected_handler_name="OSTCExtensions.OtherExampleHandlerLinux")
@@ -870,6 +901,8 @@ class TestExtension(AgentTestCase):
 
         # Test goal state not changed
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0",
                                     expected_handler_name="OSTCExtensions.OtherExampleHandlerLinux")
 
@@ -885,6 +918,7 @@ class TestExtension(AgentTestCase):
 
         with enable_invocations(dep_ext_level_3, dep_ext_level_4) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
             self._assert_ext_status(protocol.report_vm_status, "success", 1)
@@ -911,6 +945,7 @@ class TestExtension(AgentTestCase):
 
         with enable_invocations(dep_ext_level_3, dep_ext_level_4) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "NotReady", 1, "1.0.0",
                                     expected_handler_name="OSTCExtensions.OtherExampleHandlerLinux")
@@ -942,6 +977,7 @@ class TestExtension(AgentTestCase):
 
         with enable_invocations(dep_ext_level_5, dep_ext_level_6) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             self._assert_no_handler_status(protocol.report_vm_status)
             self.assertEqual(len(exthandlers_handler.ext_handlers.extHandlers), 2)
@@ -977,20 +1013,19 @@ class TestExtension(AgentTestCase):
         with patch("subprocess.Popen", mock_fail_extension_commands):
             with patch('azurelinuxagent.ga.exthandlers.add_event') as patch_add_event:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
 
                 self._assert_handler_status(protocol.report_vm_status, "NotReady", 0, "1.0.0",
                                             expected_handler_name="OSTCExtensions.OtherExampleHandlerLinux")
 
                 _assert_event_reported_only_on_incarnation_change(expected_count=1)
 
-                # Assert that on rerun it should not report errors unless incarnation changes
-                for _ in range(5):
-                    exthandlers_handler.run()
-                    _assert_event_reported_only_on_incarnation_change(expected_count=1)
-
                 test_data.set_incarnation(2)
                 protocol.update_goal_state()
+
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 # We should report error again on incarnation change
                 _assert_event_reported_only_on_incarnation_change(expected_count=2)
 
@@ -1000,6 +1035,8 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0",
                                     expected_handler_name="OSTCExtensions.OtherExampleHandlerLinux")
         self._assert_ext_status(protocol.report_vm_status, "success", 1,
@@ -1014,6 +1051,7 @@ class TestExtension(AgentTestCase):
 
         with enable_invocations(dep_ext_level_2, dep_ext_level_1) as invocation_record:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             # check handler list and dependency levels
             self.assertTrue(exthandlers_handler.ext_handlers is not None)
@@ -1034,6 +1072,8 @@ class TestExtension(AgentTestCase):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=unused-variable,no-value-for-parameter
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self.assertEqual(exthandlers_handler.ext_handlers.extHandlers[0].properties.extensions[0].dependencyLevel, 0)
         self.assertEqual(exthandlers_handler.ext_handlers.extHandlers[0].properties.extensions[0].dependencyLevel, 0)
 
@@ -1048,6 +1088,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=unused-variable,no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(exthandlers_handler.ext_handlers.extHandlers[0].properties.extensions[0].dependencyLevel, 0)
         self.assertEqual(exthandlers_handler.ext_handlers.extHandlers[0].properties.extensions[0].dependencyLevel, 0)
@@ -1101,6 +1142,8 @@ class TestExtension(AgentTestCase):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE_MULTIPLE_EXT)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=unused-variable,no-value-for-parameter
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
 
         status_path = os.path.join(conf.get_lib_dir(), AGENT_STATUS_FILE)
         actual_status_json = json.loads(fileutil.read_file(status_path))
@@ -1113,6 +1156,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1122,6 +1166,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1132,6 +1177,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1142,6 +1188,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1152,6 +1199,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "NotReady", 1, "1.1.1")
 
@@ -1161,6 +1209,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_no_handler_status(protocol.report_vm_status)
 
@@ -1169,6 +1218,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_no_handler_status(protocol.report_vm_status)
 
@@ -1178,6 +1228,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1188,6 +1239,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.2.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1198,6 +1250,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1214,6 +1267,8 @@ class TestExtension(AgentTestCase):
                     exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
                     exthandlers_handler.run()
+                    exthandlers_handler.report_ext_handlers_status()
+
                     self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
                     self._assert_ext_status(protocol.report_vm_status, "success", 0)
 
@@ -1244,6 +1299,8 @@ class TestExtension(AgentTestCase):
 
         with patch("azurelinuxagent.common.agent_supported_feature._ETPFeature.is_supported", True):
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
             self._assert_ext_status(protocol.report_vm_status, "success", 0)
 
@@ -1254,13 +1311,18 @@ class TestExtension(AgentTestCase):
             test_data.set_extensions_config_state(ExtHandlerRequestedState.Uninstall)
             test_data.set_incarnation(2)
             protocol.update_goal_state()
+
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertTrue(os.path.exists(ehi.get_extension_events_dir()), "Events directory should still exist")
 
     def test_it_should_uninstall_unregistered_extensions_properly(self, *args):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
         # Update version and set it to uninstall. That is how it would be propagated by CRP if a version 1.0.0 is
@@ -1271,7 +1333,10 @@ class TestExtension(AgentTestCase):
         test_data.manifest = test_data.manifest.replace("1.0.0", "9.9.9")
         test_data.set_incarnation(2)
         protocol.update_goal_state()
+
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         args, _ = protocol.report_vm_status.call_args
         vm_status = args[0]
         self.assertEqual(0, len(vm_status.vmAgent.extensionHandlers),
@@ -1285,8 +1350,10 @@ class TestExtension(AgentTestCase):
         protocol.report_vm_status = Mock(side_effect=ProtocolError)
 
         mock_error_state.return_value = True
+
         exthandlers_handler.run()
-        self.assertEqual(5, mock_add_event.call_count)
+        exthandlers_handler.report_ext_handlers_status()
+
         args, kw = mock_add_event.call_args
         self.assertEqual(False, kw['is_success'])
         self.assertTrue("Failed to report vm agent status" in kw['message'])
@@ -1299,11 +1366,12 @@ class TestExtension(AgentTestCase):
         protocol.report_vm_status = Mock(side_effect=ResourceGoneError)
 
         exthandlers_handler.run()
-        self.assertEqual(4, mock_add_event.call_count)
+        exthandlers_handler.report_ext_handlers_status()
+
         args, kw = mock_add_event.call_args
         self.assertEqual(False, kw['is_success'])
         self.assertTrue("ResourceGoneError" in kw['message'])
-        self.assertEqual("ExtensionProcessing", kw['op'])
+        self.assertEqual("ReportStatus", kw['op'])
 
     @patch('azurelinuxagent.common.errorstate.ErrorState.is_triggered')
     @patch('azurelinuxagent.ga.exthandlers.add_event')
@@ -1315,6 +1383,8 @@ class TestExtension(AgentTestCase):
         mock_error_state.return_value = True
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         event_occurrences = [kw for _, kw in mock_add_event.call_args_list if
                              "[ExtensionError] Failed to get ext handler pkgs" in kw['message']]
         self.assertEqual(1, len(event_occurrences))
@@ -1322,48 +1392,16 @@ class TestExtension(AgentTestCase):
         self.assertTrue("Failed to get ext handler pkgs" in event_occurrences[0]['message'])
         self.assertTrue("ProtocolError" in event_occurrences[0]['message'])
 
-    @patch('azurelinuxagent.ga.exthandlers.add_event')
-    def test_ext_handler_download_errors_should_be_reported_only_on_new_goal_state(self, mock_add_event, *args):
-
-        def _assert_mock_add_event_call(expected_download_failed_event_count, err_msg_guid):
-            event_occurrences = [kw for _, kw in mock_add_event.call_args_list if
-                          "Failed to download artifacts: [ExtensionDownloadError] {0}".format(err_msg_guid) in kw['message']]
-            self.assertEqual(expected_download_failed_event_count, len(event_occurrences), "Call count do not match")
-
-
-            self.assertFalse(any(kw['is_success'] for kw in event_occurrences), "The events should have failed")
-            self.assertEqual(expected_download_failed_event_count, len([kw['op'] for kw in event_occurrences]),
-                             "Incorrect Operation, all events should be a download errors")
-
-        test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
-        exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
-        unique_error_message_guid = str(uuid.uuid4())
-        protocol.get_ext_handler_pkgs = Mock(side_effect=ExtensionDownloadError(unique_error_message_guid))
-
-        exthandlers_handler.run()
-        _assert_mock_add_event_call(expected_download_failed_event_count=1, err_msg_guid=unique_error_message_guid)
-        self._assert_handler_status(protocol.report_vm_status, "NotReady", 0, "1.0.0")
-
-        # Re-run exthandler.run without updating the GS and ensure we dont report error
-        exthandlers_handler.run()
-        _assert_mock_add_event_call(expected_download_failed_event_count=1, err_msg_guid=unique_error_message_guid)
-        self._assert_handler_status(protocol.report_vm_status, "NotReady", 0, "1.0.0")
-
-        # Change incarnation and then re-check we report error
-        test_data.set_incarnation(2)
-        protocol.update_goal_state()
-
-        exthandlers_handler.run()
-        _assert_mock_add_event_call(expected_download_failed_event_count=2, err_msg_guid=unique_error_message_guid)
-        self._assert_handler_status(protocol.report_vm_status, "NotReady", 0, "1.0.0")
-
     @patch('azurelinuxagent.ga.exthandlers.fileutil')
     def test_ext_handler_io_error(self, mock_fileutil, *args):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=unused-variable,no-value-for-parameter
 
         mock_fileutil.write_file.return_value = IOError("Mock IO Error")
+
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
 
     def test_it_should_process_extensions_only_if_allowed(self, mock_get, mock_crypt, *args):
 
@@ -1379,6 +1417,8 @@ class TestExtension(AgentTestCase):
 
             with patch('subprocess.Popen', side_effect=mock_popen):
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 self.assertEqual(expected_call_count, len(extension_calls), "Call counts dont match")
 
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
@@ -1388,9 +1428,6 @@ class TestExtension(AgentTestCase):
         assert_extensions_called(exthandlers_handler, expected_call_count=2)
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
-        # We should not re-handle the extensions if GoalState didn't change
-        assert_extensions_called(exthandlers_handler, expected_call_count=0)
-
         # Update GoalState
         test_data.set_incarnation(2)
         protocol.update_goal_state()
@@ -1435,7 +1472,10 @@ class TestExtension(AgentTestCase):
         # Test when is_on_hold returns True - should not work
         mock_in_vm_artifacts_profile.is_on_hold = Mock(return_value=True)
         protocol.get_artifacts_profile = Mock(return_value=mock_in_vm_artifacts_profile)
+
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         vm_agent_status = protocol.report_vm_status.call_args[0][0].vmAgent
         self.assertEqual(vm_agent_status.status, "Ready", "Agent should report ready")
         self.assertEqual(0, len(vm_agent_status.extensionHandlers),
@@ -1447,6 +1487,8 @@ class TestExtension(AgentTestCase):
         mock_in_vm_artifacts_profile.is_on_hold = Mock(return_value=False)
         protocol.get_artifacts_profile = Mock(return_value=mock_in_vm_artifacts_profile)
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self.assertEqual("1", protocol.report_vm_status.call_args[0][
             0].vmAgent.vm_artifacts_aggregate_status.goal_state_aggregate_status.in_svd_seq_no, "Incarnation mismatch")
@@ -1456,36 +1498,22 @@ class TestExtension(AgentTestCase):
         # Update GoalState
         test_data.set_incarnation(2)
         protocol.update_goal_state()
+
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self.assertEqual("2", protocol.report_vm_status.call_args[0][
             0].vmAgent.vm_artifacts_aggregate_status.goal_state_aggregate_status.in_svd_seq_no, "Incarnation mismatch")
 
-    def test_last_etag_on_extension_processing(self, *args):
-        test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
-        exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
-        exthandlers_handler.ext_handlers, etag = protocol.get_ext_handlers()
-        exthandlers_handler.protocol = protocol
-
-        # Disable extension handling blocking in the first run and enable in the 2nd run
-        with patch.object(exthandlers_handler, '_extension_processing_allowed', side_effect=[False, True]):
-            exthandlers_handler.run()
-            self.assertIsNone(exthandlers_handler.last_etag,
-                              "The last etag should be None initially as extension_processing is False")
-            self.assertNotEqual(etag, exthandlers_handler.last_etag,
-                                "Last etag and etag should not be same if extension processing is disabled")
-            exthandlers_handler.run()
-            self.assertIsNotNone(exthandlers_handler.last_etag,
-                                 "Last etag should not be none if extension processing is allowed")
-            self.assertEqual(etag, exthandlers_handler.last_etag,
-                             "Last etag and etag should be same if extension processing is enabled")
-
     def test_it_should_parse_valid_in_vm_metadata_properly(self, mock_get, mock_crypt, *args):
 
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE_IN_VM_META_DATA)
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         activity_id, correlation_id, gs_creation_time = exthandlers_handler.get_goal_state_debug_metadata()
         self.assertEqual(activity_id, "555e551c-600e-4fb4-90ba-8ab8ec28eccc", "Incorrect activity Id")
@@ -1499,6 +1527,8 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         activity_id, correlation_id, gs_creation_time = exthandlers_handler.get_goal_state_debug_metadata()
         self.assertEqual(activity_id, "NA", "Activity Id should be NA")
@@ -1511,6 +1541,8 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
     def _assert_ext_status(self, vm_agent_status, expected_status,
@@ -1531,6 +1563,8 @@ class TestExtension(AgentTestCase):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt_util, *args)
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
         command_execution_log = os.path.join(conf.get_ext_log_dir(), "OSTCExtensions.ExampleHandlerLinux",
@@ -1542,6 +1576,8 @@ class TestExtension(AgentTestCase):
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
 
         # Remove status file and re-run collecting extension status
@@ -1551,7 +1587,8 @@ class TestExtension(AgentTestCase):
         self.assertTrue(os.path.isfile(status_file))
         os.remove(status_file)
 
-        exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, ValidHandlerStatus.error, 0)
 
@@ -1578,6 +1615,8 @@ class TestExtension(AgentTestCase):
         with patch('azurelinuxagent.common.cgroupapi.subprocess.Popen', side_effect=mock_popen):
             with patch('azurelinuxagent.ga.exthandlers._DEFAULT_EXT_TIMEOUT_MINUTES', 0.001):
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
 
                 # The Handler Status for the base extension should be ready as it was executed successfully by the agent
                 self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0",
@@ -1619,6 +1658,7 @@ class TestExtension(AgentTestCase):
 
         with patch('azurelinuxagent.common.cgroupapi.subprocess.Popen', side_effect=mock_popen):
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             # The Handler Status for the base extension should be ready as it was executed successfully by the agent
             self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0",
@@ -1637,6 +1677,7 @@ class TestExtension(AgentTestCase):
             mockwiredata.WireProtocolData(mockwiredata.DATA_FILE_EXT_SEQUENCING), mock_http_get, mock_crypt_util, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0",
                                     expected_handler_name="OSTCExtensions.OtherExampleHandlerLinux",
@@ -1668,6 +1709,7 @@ class TestExtension(AgentTestCase):
 
         with patch('azurelinuxagent.common.cgroupapi.subprocess.Popen', side_effect=mock_popen):
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             # The Handler Status for the base extension should be NotReady as it failed
             self._assert_handler_status(protocol.report_vm_status, "NotReady", 0, "1.0.0",
@@ -1830,13 +1872,18 @@ class TestExtension(AgentTestCase):
         # test status is reported for no extensions
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE_NO_EXT)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
+
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_no_handler_status(protocol.report_vm_status)
 
         # test status is reported, but extensions are not processed
         test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_no_handler_status(protocol.report_vm_status)
 
     def test_extensions_deleted(self, *args):
@@ -1845,6 +1892,7 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1857,6 +1905,7 @@ class TestExtension(AgentTestCase):
 
         # Ensure new extension can be enabled
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.1")
         self._assert_ext_status(protocol.report_vm_status, "success", 0)
@@ -1873,17 +1922,14 @@ class TestExtension(AgentTestCase):
 
         # Ensure initial install is unsuccessful
         patch_get_install_command.return_value = "exit.sh 1"
+
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(1, patch_install.call_count)
         self.assertEqual(1, protocol.report_vm_status.call_count)
         self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=0, version="1.0.0")
 
-        # Ensure subsequent no further retries are made
-        exthandlers_handler.run()
-        self.assertEqual(1, patch_install.call_count)
-        self.assertEqual(2, protocol.report_vm_status.call_count)
-
     @patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_install_command')
     def test_install_failure_check_exception_handling(self, patch_get_install_command, *args):
         """
@@ -1895,32 +1941,12 @@ class TestExtension(AgentTestCase):
         # Ensure install is unsuccessful
         patch_get_install_command.return_value = "exit.sh 1"
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(1, protocol.report_vm_status.call_count)
         self._assert_handler_status(protocol.report_vm_status, expected_status="NotReady", expected_ext_count=0,
                                     version="1.0.0")
 
-    @patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_enable_command')
-    def test_enable_failure(self, patch_get_enable_command, *args):
-        """
-        When extension enable fails, the operation should not be retried.
-        """
-        test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE_EXT_SINGLE)
-        exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
-
-        # Ensure initial install is successful, but enable fails
-        patch_get_enable_command.call_count = 0
-        patch_get_enable_command.return_value = "exit.sh 1"
-        exthandlers_handler.run()
-
-        self.assertEqual(1, patch_get_enable_command.call_count)
-        self.assertEqual(1, protocol.report_vm_status.call_count)
-        self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=1, version="1.0.0")
-
-        exthandlers_handler.run()
-        self.assertEqual(1, patch_get_enable_command.call_count)
-        self.assertEqual(2, protocol.report_vm_status.call_count)
-
     @patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_enable_command')
     def test_enable_failure_check_exception_handling(self, patch_get_enable_command, *args):
         """
@@ -1933,47 +1959,12 @@ class TestExtension(AgentTestCase):
         patch_get_enable_command.call_count = 0
         patch_get_enable_command.return_value = "exit.sh 1"
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(1, patch_get_enable_command.call_count)
         self.assertEqual(1, protocol.report_vm_status.call_count)
         self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=1, version="1.0.0")
 
-    @patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_disable_command')
-    def test_disable_failure(self, patch_get_disable_command, *args):
-        """
-        When extension disable fails, the operation should not be retried.
-        """
-        # Ensure initial install and enable is successful, but disable fails
-        test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE_EXT_SINGLE)
-        exthandlers_handler, protocol = self._create_mock(test_data, *args)  # pylint: disable=no-value-for-parameter
-        patch_get_disable_command.call_count = 0
-        patch_get_disable_command.return_value = "exit.sh 1"
-
-        exthandlers_handler.run()
-
-        self.assertEqual(0, patch_get_disable_command.call_count)
-        self.assertEqual(1, protocol.report_vm_status.call_count)
-        self._assert_handler_status(protocol.report_vm_status, "Ready", expected_ext_count=1, version="1.0.0")
-        self._assert_ext_status(protocol.report_vm_status, "success", 0)
-
-        # Next incarnation, disable extension
-        test_data.set_incarnation(2)
-        test_data.set_extensions_config_state(ExtHandlerRequestedState.Disabled)
-        protocol.update_goal_state()
-
-        exthandlers_handler.run()
-
-        self.assertEqual(1, patch_get_disable_command.call_count)
-        self.assertEqual(2, protocol.report_vm_status.call_count)
-        self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=1, version="1.0.0")
-
-        # Ensure there are no further retries
-        exthandlers_handler.run()
-
-        self.assertEqual(1, patch_get_disable_command.call_count)
-        self.assertEqual(3, protocol.report_vm_status.call_count)
-        self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=1, version="1.0.0")
-
     @patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_disable_command')
     def test_disable_failure_with_exception_handling(self, patch_get_disable_command,
                                                      *args):
@@ -1987,6 +1978,7 @@ class TestExtension(AgentTestCase):
         patch_get_disable_command.return_value = "exit 1"
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(0, patch_get_disable_command.call_count)
         self.assertEqual(1, protocol.report_vm_status.call_count)
@@ -1999,6 +1991,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(1, patch_get_disable_command.call_count)
         self.assertEqual(2, protocol.report_vm_status.call_count)
@@ -2016,6 +2009,7 @@ class TestExtension(AgentTestCase):
         patch_get_uninstall_command.return_value = "exit 1"
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(0, patch_get_uninstall_command.call_count)
         self.assertEqual(1, protocol.report_vm_status.call_count)
@@ -2028,6 +2022,7 @@ class TestExtension(AgentTestCase):
         protocol.update_goal_state()
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(1, patch_get_uninstall_command.call_count)
         self.assertEqual(2, protocol.report_vm_status.call_count)
@@ -2036,6 +2031,7 @@ class TestExtension(AgentTestCase):
 
         # Ensure there are no further retries
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         self.assertEqual(1, patch_get_uninstall_command.call_count)
         self.assertEqual(3, protocol.report_vm_status.call_count)
@@ -2064,6 +2060,8 @@ class TestExtension(AgentTestCase):
 
         with patch('azurelinuxagent.common.cgroupapi.subprocess.Popen', side_effect=mock_popen):
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             update_command_count = len([extension_call for extension_call in extension_calls
                                         if patch_get_update_command.return_value in extension_call])
             enable_command_count = len([extension_call for extension_call in extension_calls
@@ -2075,24 +2073,13 @@ class TestExtension(AgentTestCase):
             # We report the failure of the new extension version
             self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=1, version="1.0.1")
 
-            # Ensure we are processing the same goal state only once
-            loop_run = 5
-            for x in range(loop_run):  # pylint: disable=unused-variable
-                exthandlers_handler.run()
-
-            update_command_count = len([extension_call for extension_call in extension_calls
-                                        if patch_get_update_command.return_value in extension_call])
-            enable_command_count = len([extension_call for extension_call in extension_calls
-                                        if "-enable" in extension_call])
-            self.assertEqual(1, update_command_count)
-            self.assertEqual(0, enable_command_count)
-
             # If the incarnation number changes (there's a new goal state), ensure we go through the entire upgrade
             # process again.
             test_data.set_incarnation(3)
             protocol.update_goal_state()
 
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             update_command_count = len([extension_call for extension_call in extension_calls
                                         if patch_get_update_command.return_value in extension_call])
@@ -2110,6 +2097,7 @@ class TestExtension(AgentTestCase):
 
         with patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_enable_command') as patch_get_enable_command:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             # When the previous version's disable fails, we expect the upgrade scenario to fail, so the enable
             # for the new version is not called and the new version handler's status is reported as not ready.
@@ -2117,14 +2105,6 @@ class TestExtension(AgentTestCase):
             self.assertEqual(0, patch_get_enable_command.call_count)
             self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=0, version="1.0.1")
 
-            # Ensure we are processing the same goal state only once
-            loop_run = 5
-            for x in range(loop_run):  # pylint: disable=unused-variable
-                exthandlers_handler.run()
-
-            self.assertEqual(1, patch_get_disable_command.call_count)
-            self.assertEqual(0, patch_get_enable_command.call_count)
-
     @patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_disable_command')
     def test_extension_upgrade_failure_when_prev_version_disable_fails_and_recovers_on_next_incarnation(self, patch_get_disable_command,
                                                                                                          *args):
@@ -2133,6 +2113,7 @@ class TestExtension(AgentTestCase):
 
         with patch('azurelinuxagent.ga.exthandlers.HandlerManifest.get_enable_command') as patch_get_enable_command:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             # When the previous version's disable fails, we expect the upgrade scenario to fail, so the enable
             # for the new version is not called and the new version handler's status is reported as not ready.
@@ -2140,14 +2121,6 @@ class TestExtension(AgentTestCase):
             self.assertEqual(0, patch_get_enable_command.call_count)
             self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=0, version="1.0.1")
 
-            # Ensure we are processing the same goal state only once
-            loop_run = 5
-            for x in range(loop_run):  # pylint: disable=unused-variable
-                exthandlers_handler.run()
-
-            self.assertEqual(1, patch_get_disable_command.call_count)
-            self.assertEqual(0, patch_get_enable_command.call_count)
-
             # Force a new goal state incarnation, only then will we attempt the upgrade again
             test_data.set_incarnation(3)
             protocol.update_goal_state()
@@ -2155,6 +2128,8 @@ class TestExtension(AgentTestCase):
             # Ensure disable won't fail by making launch_command a no-op
             with patch('azurelinuxagent.ga.exthandlers.ExtHandlerInstance.launch_command') as patch_launch_command:  # pylint: disable=unused-variable
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 self.assertEqual(2, patch_get_disable_command.call_count)
                 self.assertEqual(1, patch_get_enable_command.call_count)
                 self._assert_handler_status(protocol.report_vm_status, "Ready", expected_ext_count=1, version="1.0.1")
@@ -2173,6 +2148,8 @@ class TestExtension(AgentTestCase):
                     patch_zipfile_extractall.side_effect = raise_ioerror
                     # The zipfile was corrupt and the upgrade sequence failed
                     exthandlers_handler.run()
+                    exthandlers_handler.report_ext_handlers_status()
+
 
                     # We never called the disable of the old version due to the failure when unzipping the new version,
                     # nor the enable of the new version
@@ -2183,6 +2160,7 @@ class TestExtension(AgentTestCase):
                     loop_run = 5
                     for x in range(loop_run):  # pylint: disable=unused-variable
                         exthandlers_handler.run()
+                        exthandlers_handler.report_ext_handlers_status()
 
                     self.assertEqual(0, patch_get_disable_command.call_count)
                     self.assertEqual(0, patch_get_enable_command.call_count)
@@ -2194,7 +2172,9 @@ class TestExtension(AgentTestCase):
                                                                                           *args)
 
         with patch.object(ExtHandlerInstance, "report_event", autospec=True) as patch_report_event:
-            exthandlers_handler.run()  # Download the new update the first time, and then we patch the download method.
+            exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(1, patch_get_disable_command.call_count)
 
             old_version_args, old_version_kwargs = patch_report_event.call_args
@@ -2227,6 +2207,8 @@ class TestExtension(AgentTestCase):
                                                                                           *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self.assertEqual(1, patch_get_update_command.call_count)
         self._assert_handler_status(protocol.report_vm_status, "NotReady", expected_ext_count=1, version="1.0.1")
 
@@ -2240,6 +2222,8 @@ class TestExtension(AgentTestCase):
                 as mock_continue_on_update_failure:
             # These are just testing the mocks have been called and asserting the test conditions have been met
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(1, patch_get_disable_command.call_count)
             self.assertEqual(2, mock_continue_on_update_failure.call_count,
                              "This should be called twice, for both disable and uninstall")
@@ -2258,6 +2242,8 @@ class TestExtension(AgentTestCase):
                 as mock_continue_on_update_failure:
             # These are just testing the mocks have been called and asserting the test conditions have been met
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(1, patch_get_uninstall_command.call_count)
             self.assertEqual(2, mock_continue_on_update_failure.call_count,
                              "This should be called twice, for both disable and uninstall")
@@ -2276,6 +2262,8 @@ class TestExtension(AgentTestCase):
                 as mock_continue_on_update_failure:
             # These are just testing the mocks have been called and asserting the test conditions have been met
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(1, patch_get_disable_command.call_count)
             self.assertEqual(1, mock_continue_on_update_failure.call_count,
                              "The first call would raise an exception")
@@ -2293,6 +2281,8 @@ class TestExtension(AgentTestCase):
                 as mock_continue_on_update_failure:
             # These are just testing the mocks have been called and asserting the test conditions have been met
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             self.assertEqual(1, patch_get_uninstall_command.call_count)
             self.assertEqual(2, mock_continue_on_update_failure.call_count,
                              "The second call would raise an exception")
@@ -2312,6 +2302,8 @@ class TestExtension(AgentTestCase):
                     as patch_get_enable:
                 # These are just testing the mocks have been called and asserting the test conditions have been met
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 self.assertEqual(1, patch_get_disable_command.call_count)
                 self.assertEqual(2, mock_continue_on_update_failure.call_count)
                 self.assertEqual(1, patch_get_enable.call_count)
@@ -2328,6 +2320,8 @@ class TestExtension(AgentTestCase):
                           side_effect=[ExtensionError("Disable Failed"), "ok", ExtensionError("uninstall failed"),
                                        "ok", "ok", "New enable run ok"]) as patch_start_cmd:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             _, update_kwargs = patch_start_cmd.call_args_list[1]
             _, install_kwargs = patch_start_cmd.call_args_list[3]
             _, enable_kwargs = patch_start_cmd.call_args_list[4]
@@ -2355,6 +2349,8 @@ class TestExtension(AgentTestCase):
             protocol.update_goal_state()
 
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
+
             _, new_enable_kwargs = patch_start_cmd.call_args
 
             # Ensure the new run didn't have Disable Return Code env variable
@@ -2375,6 +2371,7 @@ class TestExtension(AgentTestCase):
 
         with patch.object(CGroupConfigurator.get_instance(), "start_extension_command") as patch_start_cmd:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             # Extension Path and Version should be set for all launch_command calls
             for args, kwargs in patch_start_cmd.call_args_list:
@@ -2393,6 +2390,7 @@ class TestExtension(AgentTestCase):
 
         with patch("subprocess.Popen") as patch_popen:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             for _, kwargs in patch_popen.call_args_list:
                 self.assertIn(ExtCommandEnvVariable.ExtensionSeqNumber, kwargs['env'])
@@ -2409,6 +2407,7 @@ class TestExtension(AgentTestCase):
 
         with patch("subprocess.Popen") as patch_popen:
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
             for _, kwargs in patch_popen.call_args_list:
                 self.assertIn(ExtCommandEnvVariable.ExtensionSeqNumber, kwargs['env'])
@@ -2448,6 +2447,7 @@ class TestExtension(AgentTestCase):
         with patch.object(ExtHandlerInstance, "load_manifest", return_value=manifest):
             with patch.object(ExtHandlerInstance, 'report_event') as mock_report_event:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
 
                 for _, kwargs in mock_report_event.call_args_list:
                     # The output is of the format - 'testfile.sh\n[stdout]ConfigSequenceNumber=N\n[stderr]'
@@ -2469,6 +2469,7 @@ class TestExtension(AgentTestCase):
 
             with patch.object(ExtHandlerInstance, 'report_event') as mock_report_event:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
 
                 for _, kwargs in mock_report_event.call_args_list:
                     # The output is of the format - 'testfile.sh\n[stdout]ConfigSequenceNumber=N\n[stderr]'
@@ -2517,6 +2518,8 @@ class TestExtension(AgentTestCase):
         with patch("azurelinuxagent.ga.exthandlers.ExtHandlerInstance.load_manifest", return_value=manifest):
             with patch.object(ExtHandlerInstance, 'report_event') as mock_report_event:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+                exthandlers_handler.report_ext_handlers_status()
 
                 _, disable_kwargs = mock_report_event.call_args_list[1]  # pylint: disable=unused-variable
                 _, update_kwargs = mock_report_event.call_args_list[2]
@@ -2533,6 +2536,8 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt_util, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", expected_ext_count=1, version="1.0.0")
         args, _ = protocol.report_vm_status.call_args
         gs_aggregate_status = args[0].vmAgent.vm_artifacts_aggregate_status.goal_state_aggregate_status
@@ -2540,18 +2545,12 @@ class TestExtension(AgentTestCase):
         self.assertEqual(gs_aggregate_status.status, GoalStateStatus.Success, "Wrong status reported")
         self.assertEqual(gs_aggregate_status.in_svd_seq_no, "1", "Incorrect seq no")
 
-        # Running handler again without incarnation change should report the same gs_aggregate_status
-        for _ in range(5):
-            exthandlers_handler.run()
-            args, _ = protocol.report_vm_status.call_args
-            self.assertEqual(gs_aggregate_status,
-                             args[0].vmAgent.vm_artifacts_aggregate_status.goal_state_aggregate_status,
-                             "Aggregate status different")
-
         # Update incarnation and ensure the gs_aggregate_status is modified too
         test_data.set_incarnation(2)
         protocol.client.update_goal_state()
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         self._assert_handler_status(protocol.report_vm_status, "Ready", expected_ext_count=1, version="1.0.0")
         args, _ = protocol.report_vm_status.call_args
         new_gs_aggregate_status = args[0].vmAgent.vm_artifacts_aggregate_status.goal_state_aggregate_status
@@ -2578,6 +2577,8 @@ class TestExtension(AgentTestCase):
         exthandlers_handler, protocol = self._create_mock(test_data, mock_get, mock_crypt_util, *args)
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
+
         args, _ = protocol.report_vm_status.call_args
         gs_aggregate_status = args[0].vmAgent.vm_artifacts_aggregate_status.goal_state_aggregate_status
         self.assertEqual(0, len(args[0].vmAgent.extensionHandlers), "No extensions should be reported")
@@ -2661,6 +2662,7 @@ class TestExtensionSequencing(AgentTestCase):
             with patch.object(ExtHandlerInstance, "get_handler_status", ExtHandlerStatus):
                 with patch('azurelinuxagent.ga.exthandlers._DEFAULT_EXT_TIMEOUT_MINUTES', 0.01):
                     exthandlers_handler.run()
+
                     self._validate_extension_sequence(expected_sequence, exthandlers_handler)
 
     def test_handle_ext_handlers(self, *args):
@@ -2820,6 +2822,7 @@ class TestExtensionUpdateOnFailure(AgentTestCase):
 
             with enable_invocations(first_ext, upgraded_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
 
                 invocation_record.compare(
                     (first_ext, ExtensionCommandNames.INSTALL),
@@ -2834,6 +2837,8 @@ class TestExtensionUpdateOnFailure(AgentTestCase):
 
             with enable_invocations(first_ext, upgraded_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
 
                 return invocation_record
 
@@ -3360,8 +3365,7 @@ class TestAdditionalLocationsExtensions(AgentTestCase):
             if manifests_used:
                 # Still locations to try in the list; throw a fake
                 # error to make sure all of the locations get called.
-                return Exception("Failing manifest fetch from uri '{0}' for testing purposes."\
-                    .format(url))
+                return Exception("Failing manifest fetch from uri '{0}' for testing purposes.".format(url))
 
             return None
 
@@ -3369,6 +3373,7 @@ class TestAdditionalLocationsExtensions(AgentTestCase):
         with mock_wire_protocol(self.test_data, http_get_handler=manifest_location_handler) as protocol:
             exthandlers_handler = get_exthandlers_handler(protocol)
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
 
     def test_fetch_manifest_timeout_is_respected(self):
 
diff --git a/tests/ga/test_multi_config_extension.py b/tests/ga/test_multi_config_extension.py
index a6a61345..00183bf7 100644
--- a/tests/ga/test_multi_config_extension.py
+++ b/tests/ga/test_multi_config_extension.py
@@ -221,6 +221,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             return msg if with_message else None
 
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
         self.assertEqual(no_of_extensions,
                          len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                          "incorrect extensions reported")
@@ -252,6 +253,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
         protocol.mock_wire_data.set_incarnation(2)
         protocol.update_goal_state()
         exthandlers_handler.run()
+        exthandlers_handler.report_ext_handlers_status()
 
         mc_handlers = self._assert_and_get_handler_status(aggregate_status=protocol.aggregate_status,
                                                           handler_name="OSTCExtensions.ExampleHandlerLinux",
@@ -285,6 +287,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
         with self._setup_test_env(mock_manifest=True) as (exthandlers_handler, protocol, no_of_extensions):
             with enable_invocations(first_ext, second_ext, third_ext, fourth_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                  "incorrect extensions reported")
@@ -316,6 +319,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             protocol.mock_wire_data.set_extensions_config_state(ExtHandlerRequestedState.Uninstall)
             protocol.update_goal_state()
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertEqual(0, len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                              "No handler/extension status should be reported")
 
@@ -329,6 +333,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             protocol.mock_wire_data.set_incarnation(2)
             protocol.update_goal_state()
             exthandlers_handler.run()
+            exthandlers_handler.report_ext_handlers_status()
             self.assertEqual(no_of_extensions,
                              len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                              "incorrect extensions reported")
@@ -370,6 +375,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             sc_ext = extension_emulator(name="Microsoft.Powershell.ExampleExtension", install_action=fail_action)
             with enable_invocations(first_ext, second_ext, third_ext, sc_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                  "incorrect extensions reported")
@@ -415,6 +421,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             new_sc_ext = extension_emulator(name="Microsoft.Powershell.ExampleExtension", version=new_version)
             with enable_invocations(new_first_ext, new_second_ext, new_third_ext, new_sc_ext, *old_exts) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 old_first, old_second, old_third, old_fourth = old_exts
                 invocation_record.compare(
                     # Disable all enabled commands for MC before updating the Handler
@@ -471,6 +478,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             with enable_invocations(new_first_ext, new_second_ext, new_third_ext, new_sc_ext,
                                     *old_exts) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 old_first, old_second, old_third, old_fourth = old_exts
                 invocation_record.compare(
                     # Disable all enabled commands for MC before updating the Handler
@@ -537,6 +545,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             with enable_invocations(new_first_ext, new_second_ext, new_third_ext, new_fourth_ext,
                                     *old_exts) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 old_first, _, _, old_fourth = old_exts
                 invocation_record.compare(
                     # Disable for firstExtension should fail 3 times, i.e., once per extension which tries to update the Handler
@@ -590,6 +599,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             fourth_ext = extension_emulator(name="Microsoft.Powershell.ExampleExtension", enable_action=fail_action)
             with enable_invocations(first_ext, second_ext, third_ext, fourth_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                  "incorrect extensions reported")
@@ -646,6 +656,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
             protocol.update_goal_state()
 
             ext_handler.run()
+            ext_handler.report_ext_handlers_status()
             mc_handlers = self._assert_and_get_handler_status(aggregate_status=protocol.aggregate_status,
                                                               handler_name="OSTCExtensions.ExampleHandlerLinux",
                                                               expected_count=2, status="Ready")
@@ -767,6 +778,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
                 protocol.mock_wire_data.set_incarnation(2)
                 protocol.update_goal_state()
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
 
                 mc_handlers = self._assert_and_get_handler_status(aggregate_status=protocol.aggregate_status,
                                                                   handler_name="OSTCExtensions.ExampleHandlerLinux",
@@ -978,6 +990,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
         with self._setup_test_env() as (exthandlers_handler, protocol, no_of_extensions):
             with enable_invocations(first_ext, second_ext, third_ext, fourth_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                  "incorrect extensions reported")
@@ -1017,6 +1030,7 @@ class TestMultiConfigExtensions(_MultiConfigBaseTestClass):
                 with enable_invocations(*old_exts) as invocation_record:
                     (_, _, _, fourth_ext) = old_exts
                     exthandlers_handler.run()
+                    exthandlers_handler.report_ext_handlers_status()
                     self.assertEqual(4, len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                      "incorrect extensions reported")
 
@@ -1069,6 +1083,7 @@ class TestMultiConfigExtensionSequencing(_MultiConfigBaseTestClass):
                 independent_sc_ext):
             with enable_invocations(first_ext, second_ext, third_ext, dependent_sc_ext, independent_sc_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                  "incorrect extensions reported")
@@ -1144,6 +1159,7 @@ class TestMultiConfigExtensionSequencing(_MultiConfigBaseTestClass):
             with enable_invocations(first_ext, second_ext, third_ext, dependent_sc_ext,
                                     independent_sc_ext) as invocation_record:
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
                                  "incorrect extensions reported")
@@ -1197,6 +1213,7 @@ class TestMultiConfigExtensionSequencing(_MultiConfigBaseTestClass):
         with self._setup_test_env(mock_manifest=True) as (exthandlers_handler, protocol, no_of_extensions):
             with patch('azurelinuxagent.common.cgroupapi.subprocess.Popen', side_effect=mock_popen):
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
 
                 self.assertEqual(no_of_extensions,
                                  len(protocol.aggregate_status['aggregateStatus']['handlerAggregateStatus']),
diff --git a/tests/ga/test_remoteaccess_handler.py b/tests/ga/test_remoteaccess_handler.py
index dbe925cc..1e8e76e2 100644
--- a/tests/ga/test_remoteaccess_handler.py
+++ b/tests/ga/test_remoteaccess_handler.py
@@ -469,7 +469,7 @@ class TestRemoteAccessHandler(AgentTestCase):
     def test_remote_access_handler_run_error(self, _):
         with patch("azurelinuxagent.ga.remoteaccess.get_osutil", return_value=MockOSUtil()):
             mock_protocol = WireProtocol("foo.bar")
-            mock_protocol.get_incarnation = MagicMock(side_effect=Exception("foobar!"))
+            mock_protocol.client.get_remote_access = MagicMock(side_effect=Exception("foobar!"))
 
             rah = RemoteAccessHandler(mock_protocol)
             rah.run()
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index d2bda25a..e700f43d 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -43,7 +43,7 @@ from azurelinuxagent.ga.update import GuestAgent, GuestAgentError, MAX_FAILURE,
     CHILD_LAUNCH_RESTART_MAX, CHILD_HEALTH_INTERVAL, UpdateHandler
 from tests.protocol.mocks import mock_wire_protocol
 from tests.protocol.mockwiredata import DATA_FILE, DATA_FILE_MULTIPLE_EXT
-from tests.tools import AgentTestCase, call, data_dir, DEFAULT, patch, load_bin_data, load_data, Mock, MagicMock, \
+from tests.tools import AgentTestCase, data_dir, DEFAULT, patch, load_bin_data, load_data, Mock, MagicMock, \
     clear_singleton_instances, mock_sleep
 
 NO_ERROR = {
@@ -717,15 +717,16 @@ class TestUpdate(UpdateTestCase):
         UpdateTestCase.setUp(self)
         self.event_patch = patch('azurelinuxagent.common.event.add_event')
         self.update_handler = get_update_handler()
+        protocol = Mock()
+        protocol.get_ext_handlers = Mock(return_value=(Mock(), Mock()))
         self.update_handler.protocol_util = Mock()
+        self.update_handler.protocol_util.get_protocol = Mock(return_value=protocol)
 
         # Since ProtocolUtil is a singleton per thread, we need to clear it to ensure that the test cases do not reuse
         # a previous state
         clear_singleton_instances(ProtocolUtil)
 
     def test_creation(self):
-        self.assertTrue(self.update_handler.running)
-
         self.assertEqual(None, self.update_handler.last_attempt_time)
 
         self.assertEqual(0, len(self.update_handler.agents))
@@ -1246,7 +1247,7 @@ class TestUpdate(UpdateTestCase):
         self.assertEqual(0, latest_agent.error.failure_count)
 
         with patch('azurelinuxagent.ga.update.UpdateHandler.get_latest_agent', return_value=latest_agent):
-            self.update_handler.running = False
+            self.update_handler.is_running = False
             self._test_run_latest(mock_child=ChildMock(side_effect=Exception("Attempt blacklisting")))
 
         self.assertTrue(latest_agent.is_available)
@@ -1265,71 +1266,61 @@ class TestUpdate(UpdateTestCase):
         self._test_run_latest()
         self.assertEqual(0, mock_signal.call_count)
 
-    def _test_run(self, invocations=1, calls=None, enable_updates=False, sleep_interval=(6,)):
-        if calls is None:
-            calls = [call.run()]
+    def _test_run(self, invocations=1, calls=1, enable_updates=False, sleep_interval=(6,)):
         conf.get_autoupdate_enabled = Mock(return_value=enable_updates)
 
-        # Note:
-        # - Python only allows mutations of objects to which a function has
-        #   a reference. Incrementing an integer directly changes the
-        #   reference. Incrementing an item of a list changes an item to
-        #   which the code has a reference.
-        #   See http://stackoverflow.com/questions/26408941/python-nested-functions-and-variable-scope
-        iterations = [0]
-
-        def iterator(*args, **kwargs):  # pylint: disable=unused-argument
-            iterations[0] += 1
-            if iterations[0] >= invocations:
-                self.update_handler.running = False
-            return
+        def iterator(*_, **__):
+            iterator.count += 1
+            if iterator.count <= invocations:
+                return True
+            return False
+        iterator.count = 0
 
         fileutil.write_file(conf.get_agent_pid_file_path(), ustr(42))
 
         with patch('azurelinuxagent.ga.exthandlers.get_exthandlers_handler') as mock_handler:
-            with patch('azurelinuxagent.ga.remoteaccess.get_remote_access_handler') as mock_ra_handler:
-                with patch('azurelinuxagent.ga.update.get_monitor_handler') as mock_monitor:
-                    with patch('azurelinuxagent.ga.update.get_env_handler') as mock_env:
-                        with patch('azurelinuxagent.ga.update.get_collect_logs_handler') as mock_collect_logs:
-                            with patch('azurelinuxagent.ga.update.get_send_telemetry_events_handler') as mock_telemetry_send_events:
-                                with patch('azurelinuxagent.ga.update.get_collect_telemetry_events_handler') as mock_event_collector:
-                                    with patch('azurelinuxagent.ga.update.initialize_event_logger_vminfo_common_parameters'):
-                                        with patch('azurelinuxagent.ga.update.is_log_collection_allowed', return_value=True):
-                                            with patch('time.sleep', side_effect=iterator) as sleep_mock:
-                                                with patch('sys.exit') as mock_exit:
-                                                    if isinstance(os.getppid, MagicMock):
-                                                        self.update_handler.run()
-                                                    else:
-                                                        with patch('os.getppid', return_value=42):
+            mock_handler.run_ext_handlers = Mock()
+            with patch('azurelinuxagent.ga.update.get_monitor_handler') as mock_monitor:
+                with patch.object(UpdateHandler, 'is_running') as mock_is_running:
+                    mock_is_running.__get__ = Mock(side_effect=iterator)
+                    with patch('azurelinuxagent.ga.remoteaccess.get_remote_access_handler') as mock_ra_handler:
+                        with patch('azurelinuxagent.ga.update.get_env_handler') as mock_env:
+                            with patch('azurelinuxagent.ga.update.get_collect_logs_handler') as mock_collect_logs:
+                                with patch('azurelinuxagent.ga.update.get_send_telemetry_events_handler') as mock_telemetry_send_events:
+                                    with patch('azurelinuxagent.ga.update.get_collect_telemetry_events_handler') as mock_event_collector:
+                                        with patch('azurelinuxagent.ga.update.initialize_event_logger_vminfo_common_parameters'):
+                                            with patch('azurelinuxagent.ga.update.is_log_collection_allowed', return_value=True):
+                                                with patch('time.sleep') as sleep_mock:
+                                                    with patch('sys.exit') as mock_exit:
+                                                        if isinstance(os.getppid, MagicMock):
                                                             self.update_handler.run()
-
-                                                self.assertEqual(1, mock_handler.call_count)
-                                                self.assertEqual(mock_handler.return_value.method_calls, calls)
-                                                self.assertEqual(1, mock_ra_handler.call_count)
-                                                self.assertEqual(mock_ra_handler.return_value.method_calls, calls)
-                                                self.assertEqual(invocations, sleep_mock.call_count)
-                                                if invocations > 0:
-                                                    self.assertEqual(sleep_interval, sleep_mock.call_args[0])
-                                                self.assertEqual(1, mock_monitor.call_count)
-                                                self.assertEqual(1, mock_env.call_count)
-                                                self.assertEqual(1, mock_collect_logs.call_count)
-                                                self.assertEqual(1, mock_telemetry_send_events.call_count)
-                                                self.assertEqual(1, mock_event_collector.call_count)
-                                                self.assertEqual(1, mock_exit.call_count)
+                                                        else:
+                                                            with patch('os.getppid', return_value=42):
+                                                                self.update_handler.run()
+
+                                                    self.assertEqual(1, mock_handler.call_count)
+                                                    self.assertEqual(calls, len([c for c in [call[0] for call in mock_handler.return_value.method_calls] if c == 'run']))
+                                                    self.assertEqual(1, mock_ra_handler.call_count)
+                                                    self.assertEqual(calls, len(mock_ra_handler.return_value.method_calls))
+                                                    if calls > 0:
+                                                        self.assertEqual(sleep_interval, sleep_mock.call_args[0])
+                                                    self.assertEqual(1, mock_monitor.call_count)
+                                                    self.assertEqual(1, mock_env.call_count)
+                                                    self.assertEqual(1, mock_collect_logs.call_count)
+                                                    self.assertEqual(1, mock_telemetry_send_events.call_count)
+                                                    self.assertEqual(1, mock_event_collector.call_count)
+                                                    self.assertEqual(1, mock_exit.call_count)
 
     def test_run(self):
         self._test_run()
 
-    def test_run_keeps_running(self):
-        self._test_run(invocations=15, calls=[call.run()] * 15)
-
     def test_run_stops_if_update_available(self):
         self.update_handler._upgrade_available = Mock(return_value=True)
-        self._test_run(invocations=0, calls=[], enable_updates=True)
+        self._test_run(invocations=0, calls=0, enable_updates=True)
 
     def test_run_stops_if_orphaned(self):
         with patch('os.getppid', return_value=1):
-            self._test_run(invocations=0, calls=[], enable_updates=True)
+            self._test_run(invocations=0, calls=0, enable_updates=True)
 
     def test_run_clears_sentinel_on_successful_exit(self):
         self._test_run()
@@ -1337,7 +1328,7 @@ class TestUpdate(UpdateTestCase):
 
     def test_run_leaves_sentinel_on_unsuccessful_exit(self):
         self.update_handler._upgrade_available = Mock(side_effect=Exception)
-        self._test_run(invocations=0, calls=[], enable_updates=True)
+        self._test_run(invocations=1, calls=0, enable_updates=True)
         self.assertTrue(os.path.isfile(self.update_handler._sentinel_file_path()))
 
     def test_run_emits_restart_event(self):
@@ -1376,13 +1367,13 @@ class TestUpdate(UpdateTestCase):
     def test_shutdown(self):
         self.update_handler._set_sentinel()
         self.update_handler._shutdown()
-        self.assertFalse(self.update_handler.running)
+        self.assertFalse(self.update_handler.is_running)
         self.assertFalse(os.path.isfile(self.update_handler._sentinel_file_path()))
 
     def test_shutdown_ignores_missing_sentinel_file(self):
         self.assertFalse(os.path.isfile(self.update_handler._sentinel_file_path()))
         self.update_handler._shutdown()
-        self.assertFalse(self.update_handler.running)
+        self.assertFalse(self.update_handler.is_running)
         self.assertFalse(os.path.isfile(self.update_handler._sentinel_file_path()))
 
     def test_shutdown_ignores_exceptions(self):
@@ -1505,7 +1496,7 @@ class TestUpdate(UpdateTestCase):
         behavior never changes.
         """
         self.update_handler._upgrade_available = Mock(return_value=True)
-        self._test_run(invocations=0, calls=[], enable_updates=True, sleep_interval=(300,))
+        self._test_run(invocations=0, calls=0, enable_updates=True, sleep_interval=(300,))
 
     @patch('azurelinuxagent.common.conf.get_extensions_enabled', return_value=False)
     def test_interval_changes_when_extensions_disabled(self, _):
@@ -1513,7 +1504,7 @@ class TestUpdate(UpdateTestCase):
         When extension processing is disabled, the goal state interval should be larger.
         """
         self.update_handler._upgrade_available = Mock(return_value=False)
-        self._test_run(invocations=15, calls=[call.run()] * 15, sleep_interval=(300,))
+        self._test_run(invocations=1, calls=1, sleep_interval=(300,))
 
     @patch("azurelinuxagent.common.logger.info")
     @patch("azurelinuxagent.ga.update.add_event")
@@ -1561,7 +1552,7 @@ class TestUpdate(UpdateTestCase):
                     update_handler._cur_iteration = 0
                     update_handler._iterations = 0
                     update_handler.set_iterations = lambda i: _set_iterations(i)  # pylint: disable=unnecessary-lambda
-                    type(update_handler).running = PropertyMock(side_effect=check_running)
+                    type(update_handler).is_running = PropertyMock(side_effect=check_running)
                     with patch("time.sleep", side_effect=lambda _: mock_sleep(0.001)):
                         with patch('sys.exit'):
                             # Setup the initial number of iterations
@@ -1571,7 +1562,7 @@ class TestUpdate(UpdateTestCase):
                             finally:
                                 # Since PropertyMock requires us to mock the type(ClassName).property of the object,
                                 # reverting it back to keep the state of the test clean
-                                type(update_handler).running = True
+                                type(update_handler).is_running = True
 
     @staticmethod
     def _get_test_ext_handler_instance(protocol, name="OSTCExtensions.ExampleHandlerLinux", version="1.0.0"):
@@ -1701,34 +1692,36 @@ class MonitorThreadTest(AgentTestCase):
         AgentTestCase.setUp(self)
         self.event_patch = patch('azurelinuxagent.common.event.add_event')
         currentThread().setName("ExtHandler")
+        protocol = Mock()
+        protocol.get_ext_handlers = Mock(return_value=(Mock(), Mock()))
         self.update_handler = get_update_handler()
         self.update_handler.protocol_util = Mock()
+        self.update_handler.protocol_util.get_protocol = Mock(return_value=protocol)
         clear_singleton_instances(ProtocolUtil)
 
     def _test_run(self, invocations=1):
-        iterations = [0]
-
-        def iterator(*args, **kwargs):  # pylint: disable=unused-argument
-            iterations[0] += 1
-            if iterations[0] >= invocations:
-                self.update_handler.running = False
-            return
+        def iterator(*_, **__):
+            iterator.count += 1
+            if iterator.count <= invocations:
+                return True
+            return False
+        iterator.count = 0
 
         with patch('os.getpid', return_value=42):
             with patch.object(UpdateHandler, '_is_orphaned') as mock_is_orphaned:
                 mock_is_orphaned.__get__ = Mock(return_value=False)
-                with patch('azurelinuxagent.ga.exthandlers.get_exthandlers_handler'):
-                    with patch('azurelinuxagent.ga.remoteaccess.get_remote_access_handler'):
-                        with patch('azurelinuxagent.ga.update.initialize_event_logger_vminfo_common_parameters'):
-                            with patch('azurelinuxagent.common.cgroupapi.CGroupsApi.cgroups_supported', return_value=False):  # skip all cgroup stuff
-                                with patch('azurelinuxagent.ga.update.is_log_collection_allowed', return_value=True):
-                                    with patch('time.sleep', side_effect=iterator):
-                                        with patch('sys.exit'):
-                                            self.update_handler.run()
+                with patch.object(UpdateHandler, 'is_running') as mock_is_running:
+                    mock_is_running.__get__ = Mock(side_effect=iterator)
+                    with patch('azurelinuxagent.ga.exthandlers.get_exthandlers_handler'):
+                        with patch('azurelinuxagent.ga.remoteaccess.get_remote_access_handler'):
+                            with patch('azurelinuxagent.ga.update.initialize_event_logger_vminfo_common_parameters'):
+                                with patch('azurelinuxagent.common.cgroupapi.CGroupsApi.cgroups_supported', return_value=False):  # skip all cgroup stuff
+                                    with patch('azurelinuxagent.ga.update.is_log_collection_allowed', return_value=True):
+                                        with patch('time.sleep'):
+                                            with patch('sys.exit'):
+                                                self.update_handler.run()
 
     def _setup_mock_thread_and_start_test_run(self, mock_thread, is_alive=True, invocations=0):
-        self.assertTrue(self.update_handler.running)
-
         thread = MagicMock()
         thread.run = MagicMock()
         thread.is_alive = MagicMock(return_value=is_alive)
@@ -1739,8 +1732,6 @@ class MonitorThreadTest(AgentTestCase):
         return thread
 
     def test_start_threads(self, mock_env, mock_monitor, mock_collect_logs, mock_telemetry_send_events, mock_telemetry_collector):
-        self.assertTrue(self.update_handler.running)
-
         def _get_mock_thread():
             thread = MagicMock()
             thread.run = MagicMock()
@@ -1758,7 +1749,7 @@ class MonitorThreadTest(AgentTestCase):
             self.assertEqual(1, thread().run.call_count)
 
     def test_check_if_monitor_thread_is_alive(self, _, mock_monitor, *args):  # pylint: disable=unused-argument
-        mock_monitor_thread = self._setup_mock_thread_and_start_test_run(mock_monitor, is_alive=True, invocations=0)
+        mock_monitor_thread = self._setup_mock_thread_and_start_test_run(mock_monitor, is_alive=True, invocations=1)
         self.assertEqual(1, mock_monitor.call_count)
         self.assertEqual(1, mock_monitor_thread.run.call_count)
         self.assertEqual(1, mock_monitor_thread.is_alive.call_count)
@@ -1786,14 +1777,14 @@ class MonitorThreadTest(AgentTestCase):
         self.assertEqual(1, mock_env_thread.start.call_count)
 
     def test_restart_monitor_thread(self, _, mock_monitor, *args):  # pylint: disable=unused-argument
-        mock_monitor_thread = self._setup_mock_thread_and_start_test_run(mock_monitor, is_alive=False, invocations=0)
+        mock_monitor_thread = self._setup_mock_thread_and_start_test_run(mock_monitor, is_alive=False, invocations=1)
         self.assertEqual(True, mock_monitor.called)
         self.assertEqual(True, mock_monitor_thread.run.called)
         self.assertEqual(True, mock_monitor_thread.is_alive.called)
         self.assertEqual(True, mock_monitor_thread.start.called)
 
     def test_restart_env_thread(self, mock_env, *args):  # pylint: disable=unused-argument
-        mock_env_thread = self._setup_mock_thread_and_start_test_run(mock_env, is_alive=False, invocations=0)
+        mock_env_thread = self._setup_mock_thread_and_start_test_run(mock_env, is_alive=False, invocations=1)
         self.assertEqual(True, mock_env.called)
         self.assertEqual(True, mock_env_thread.run.called)
         self.assertEqual(True, mock_env_thread.is_alive.called)
@@ -1901,5 +1892,40 @@ class TimeMock(Mock):
         return current_time
 
 
+class TestProcessGoalState(AgentTestCase):
+    """
+    Tests for UpdateHandler._process_goal_state
+    """
+    def test_it_should_process_goal_state_only_on_new_goal_state(self):
+        with mock_wire_protocol(DATA_FILE) as protocol:
+            update_handler = get_update_handler()
+            with patch.object(update_handler, "_upgrade_available", return_value=False):  # skip the upgrade logic
+                exthandlers_handler = Mock()
+                remote_access_handler = Mock()
+
+                def get_method_calls(mock, method):
+                    return [call[0] for call in mock.method_calls if call[0] == method]
+
+                # process a goal state
+                update_handler._process_goal_state(protocol, exthandlers_handler, remote_access_handler)
+                self.assertEqual(1, len(get_method_calls(exthandlers_handler, 'run')), "exthandlers_handler.run() should have been called on the first goal state")
+                self.assertEqual(1, len(get_method_calls(exthandlers_handler, 'report_ext_handlers_status')), "exthandlers_handler.report_ext_handlers_status() should have been called on the first goal state")
+                self.assertEqual(1, len(get_method_calls(remote_access_handler, 'run')), "remote_access_handler.run() should have been called on the first goal state")
+
+                # process the same goal state
+                update_handler._process_goal_state(protocol, exthandlers_handler, remote_access_handler)
+                self.assertEqual(1, len(get_method_calls(exthandlers_handler, 'run')), "exthandlers_handler.run() should have not been called on the same goal state")
+                self.assertEqual(2, len(get_method_calls(exthandlers_handler, 'report_ext_handlers_status')), "exthandlers_handler.report_ext_handlers_status() should have been called on the same goal state")
+                self.assertEqual(1, len(get_method_calls(remote_access_handler, 'run')), "remote_access_handler.run() should not have been called on the same goal state")
+
+                # process a new goal state
+                protocol.mock_wire_data.set_incarnation(999)
+                protocol.client.update_goal_state()
+                update_handler._process_goal_state(protocol, exthandlers_handler, remote_access_handler)
+                self.assertEqual(2, len(get_method_calls(exthandlers_handler, 'run')), "exthandlers_handler.run() should have been called on a new goal state")
+                self.assertEqual(3, len(get_method_calls(exthandlers_handler, 'report_ext_handlers_status')), "exthandlers_handler.report_ext_handlers_status() should have been called on a new goal state")
+                self.assertEqual(2, len(get_method_calls(remote_access_handler, 'run')), "remote_access_handler.run() should have been called on a new goal state")
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py
index 5efe0d46..fd7a6300 100644
--- a/tests/protocol/test_wire.py
+++ b/tests/protocol/test_wire.py
@@ -393,6 +393,8 @@ class TestWireProtocol(AgentTestCase):
 
             with patch("azurelinuxagent.common.agent_supported_feature._MultiConfigFeature.is_supported", True):
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 self.assertIsNotNone(protocol.aggregate_status, "Aggregate status should not be None")
                 self.assertIn("supportedFeatures", protocol.aggregate_status, "supported features not reported")
                 multi_config_feature = get_supported_feature_by_name(SupportedFeatureNames.MultiConfig)
@@ -406,6 +408,8 @@ class TestWireProtocol(AgentTestCase):
             # Feature should not be reported if not present
             with patch("azurelinuxagent.common.agent_supported_feature._MultiConfigFeature.is_supported", False):
                 exthandlers_handler.run()
+                exthandlers_handler.report_ext_handlers_status()
+
                 self.assertIsNotNone(protocol.aggregate_status, "Aggregate status should not be None")
                 if "supportedFeatures" not in protocol.aggregate_status:
                     # In the case Multi-config was the only feature available, 'supportedFeatures' should not be