Repository: Azure/WALinuxAgent
Pull request #2015 (2020-09-22 16:46:08)

Verify that the CPU and Memory cgroups for the agent are properly initialized; disabled cgroups if they are not active.
On a small number of machines the controllers for CPU and/or memory were not active and the agent's cgroups were initialized to None. The resource usage data was not being collected, as expected, but the check for processes in the agent's cgroup was still being done, generating false positives about cgroups not working properly.

Also, added a check to disable cgroups if both cgroups are None.

On another set of machines, the agent was within the init.scope, or directly under services.slice; added a check to disable cgroups if the agent is not in services.slice/walinuxagent.service.

I also converted some WARNING messages to INFO, since there were a couple of questions when people noticed them in the agent log.

Lastly, addressed linter warnings in the code I touched.


Patch:
diff --git a/azurelinuxagent/common/cgroupconfigurator.py b/azurelinuxagent/common/cgroupconfigurator.py
index 6de14c0d..932a1d14 100644
--- a/azurelinuxagent/common/cgroupconfigurator.py
+++ b/azurelinuxagent/common/cgroupconfigurator.py
@@ -35,7 +35,9 @@ class CGroupConfigurator(object):
 
     NOTE: with the exception of start_extension_command, none of the methods in this class raise exceptions (cgroup operations should not block extensions)
     """
-    class __impl(object): # pylint: disable=R0902,C0103
+    # too-many-instance-attributes<R0902> Disabled: class complexity is OK
+    # invalid-name<C0103> Disabled: class is private, so name starts with __
+    class __Impl(object):  # pylint: disable=R0902,C0103
         def __init__(self):
             self._initialized = False
             self._cgroups_supported = False
@@ -46,7 +48,8 @@ class CGroupConfigurator(object):
             self._get_processes_in_agent_cgroup_last_error = None
             self._get_processes_in_agent_cgroup_error_count = 0
 
-        def initialize(self): # pylint: disable=R0912
+        # too-many-branches<R0912> Disabled: branches are sequential, not nested
+        def initialize(self):  # pylint: disable=R0912
             # pylint: disable=too-many-locals
             try:
                 if self._initialized:
@@ -76,12 +79,7 @@ class CGroupConfigurator(object):
                     logger.info(message)
                     add_event(op=WALAEventOperation.CGroupsInfo, message=message)
 
-                def log_cgroup_warn(format_string, *args):
-                    message = format_string.format(*args)
-                    logger.warn(message)
-                    add_event(op=WALAEventOperation.CGroupsInfo, message=message, is_success=False, log_event=False)
-
-                log_cgroup_info("systemd version: {0}", self._cgroups_api.get_systemd_version()) # pylint: disable=E1101
+                log_cgroup_info("systemd version: {0}", self._cgroups_api.get_systemd_version())  # pylint: disable=E1101
 
                 #
                 # Older versions of the daemon (2.2.31-2.2.40) wrote their PID to /sys/fs/cgroup/{cpu,memory}/WALinuxAgent/WALinuxAgent.  When running
@@ -90,69 +88,79 @@ class CGroupConfigurator(object):
                 legacy_cgroups = self._cgroups_api.cleanup_legacy_cgroups()
 
                 if legacy_cgroups > 0:
-                    log_cgroup_warn("The daemon's PID was added to a legacy cgroup; will not monitor resource usage.")
+                    log_cgroup_info("The daemon's PID was added to a legacy cgroup; will not monitor resource usage.")
                     return
 
                 #
                 # check v1 controllers
                 #
-                cpu_controller_root, memory_controller_root = self._cgroups_api.get_cgroup_mount_points() # pylint: disable=E1101
+                cpu_controller_root, memory_controller_root = self._cgroups_api.get_cgroup_mount_points()  # pylint: disable=E1101
 
                 if cpu_controller_root is not None:
                     logger.info("The CPU cgroup controller is mounted at {0}", cpu_controller_root)
                 else:
-                    log_cgroup_warn("The CPU cgroup controller is not mounted")
+                    log_cgroup_info("The CPU cgroup controller is not mounted")
 
                 if memory_controller_root is not None:
                     logger.info("The memory cgroup controller is mounted at {0}", memory_controller_root)
                 else:
-                    log_cgroup_warn("The memory cgroup controller is not mounted")
+                    log_cgroup_info("The memory cgroup controller is not mounted")
 
                 #
                 # check v2 controllers
                 #
-                cgroup2_mountpoint, cgroup2_controllers = self._cgroups_api.get_cgroup2_controllers() # pylint: disable=E1101
-                if cgroup2_mountpoint is not None:
-                    log_cgroup_warn("cgroups v2 mounted at {0}.  Controllers: [{1}]", cgroup2_mountpoint, cgroup2_controllers)
+                cgroup2_mount_point, cgroup2_controllers = self._cgroups_api.get_cgroup2_controllers()  # pylint: disable=E1101
+                if cgroup2_mount_point is not None:
+                    log_cgroup_info("cgroups v2 mounted at {0}.  Controllers: [{1}]", cgroup2_mount_point, cgroup2_controllers)
 
                 #
                 # check the cgroups for the agent
                 #
-                agent_unit_name = self._cgroups_api.get_agent_unit_name() # pylint: disable=E1101
-                cpu_cgroup_relative_path, memory_cgroup_relative_path = self._cgroups_api.get_process_cgroup_relative_paths("self") # pylint: disable=E1101
+                agent_unit_name = self._cgroups_api.get_agent_unit_name()  # pylint: disable=E1101
+                cpu_cgroup_relative_path, memory_cgroup_relative_path = self._cgroups_api.get_process_cgroup_relative_paths("self")  # pylint: disable=E1101
+                expected_relative_path = os.path.join('system.slice', agent_unit_name)
                 if cpu_cgroup_relative_path is None:
-                    log_cgroup_warn("The agent's process is not within a CPU cgroup")
+                    log_cgroup_info("The agent's process is not within a CPU cgroup")
                 else:
-                    cpu_accounting = self._cgroups_api.get_unit_property(agent_unit_name, "CPUAccounting") # pylint: disable=E1101
+                    if cpu_cgroup_relative_path != expected_relative_path:
+                        log_cgroup_info("The Agent is not in the expected cgroup; will not enable cgroup monitoring. CPU relative path:[{0}] Expected:[{1}]", cpu_cgroup_relative_path, expected_relative_path)
+                        return
+                    cpu_accounting = self._cgroups_api.get_unit_property(agent_unit_name, "CPUAccounting")  # pylint: disable=E1101
                     log_cgroup_info('CPUAccounting: {0}', cpu_accounting)
 
                 if memory_cgroup_relative_path is None:
-                    log_cgroup_warn("The agent's process is not within a memory cgroup")
+                    log_cgroup_info("The agent's process is not within a memory cgroup")
                 else:
-                    memory_accounting = self._cgroups_api.get_unit_property(agent_unit_name, "MemoryAccounting") # pylint: disable=E1101
+                    if memory_cgroup_relative_path != expected_relative_path:
+                        log_cgroup_info("The Agent is not in the expected cgroup; will not enable cgroup monitoring. Memory relative path:[{0}] Expected:[{1}]", memory_cgroup_relative_path, expected_relative_path)
+                        return
+                    memory_accounting = self._cgroups_api.get_unit_property(agent_unit_name, "MemoryAccounting")  # pylint: disable=E1101
                     log_cgroup_info('MemoryAccounting: {0}', memory_accounting)
 
                 #
                 # All good, enable cgroups and start monitoring the agent
                 #
-                self._cgroups_enabled = True
-
                 if cpu_controller_root is None or cpu_cgroup_relative_path is None:
                     logger.info("Will not track CPU for the agent's cgroup")
                 else:
                     self._agent_cpu_cgroup_path = os.path.join(cpu_controller_root, cpu_cgroup_relative_path)
+                    log_cgroup_info("Agent CPU cgroup: {0}", self._agent_cpu_cgroup_path)
                     CGroupsTelemetry.track_cgroup(CpuCgroup(agent_unit_name, self._agent_cpu_cgroup_path))
 
                 if memory_controller_root is None or memory_cgroup_relative_path is None:
                     logger.info("Will not track memory for the agent's cgroup")
                 else:
                     self._agent_memory_cgroup_path = os.path.join(memory_controller_root, memory_cgroup_relative_path)
+                    log_cgroup_info("Agent Memory cgroup: {0}", self._agent_memory_cgroup_path)
                     CGroupsTelemetry.track_cgroup(MemoryCgroup(agent_unit_name, self._agent_memory_cgroup_path))
 
-                log_cgroup_info("Agent cgroups: CPU: {0} -- MEMORY: {1}", self._agent_cpu_cgroup_path, self._agent_memory_cgroup_path)
+                if self._agent_cpu_cgroup_path is not None or self._agent_memory_cgroup_path is not None:
+                    self._cgroups_enabled = True
+
+                log_cgroup_info('Cgroups enabled: {0}', self._cgroups_enabled)
 
-            except Exception as e: # pylint: disable=C0103
-                message = "Error initializing cgroups: {0}".format(ustr(e))
+            except Exception as exception:
+                message = "Error initializing cgroups: {0}".format(ustr(exception))
                 logger.warn(message)
                 add_event(op=WALAEventOperation.CGroupsInitialize, is_success=False, message=message, log_event=False)
             finally:
@@ -183,13 +191,13 @@ class CGroupConfigurator(object):
 
             try:
                 return operation()
-            except Exception as e: # pylint: disable=C0103
-                logger.warn("{0} Error: {1}".format(error_message, ustr(e)))
+            except Exception as exception:
+                logger.warn("{0} Error: {1}".format(error_message, ustr(exception)))
                 if on_error is not None:
                     try:
-                        on_error(e)
-                    except Exception as ex: # pylint: disable=W0612
-                        logger.warn("CGroupConfigurator._invoke_cgroup_operation: {0}".format(ustr(e)))
+                        on_error(exception)
+                    except Exception as exception:
+                        logger.warn("CGroupConfigurator._invoke_cgroup_operation: {0}".format(ustr(exception)))
 
         def create_extension_cgroups_root(self):
             """
@@ -227,7 +235,7 @@ class CGroupConfigurator(object):
             def __impl():
                 if self._agent_cpu_cgroup_path is None:
                     return []
-                return self._cgroups_api.get_processes_in_cgroup(self._agent_cpu_cgroup_path) # pylint: disable=E1101
+                return self._cgroups_api.get_processes_in_cgroup(self._agent_cpu_cgroup_path)  # pylint: disable=E1101
 
             def __on_error(exception):
                 #
@@ -242,8 +250,8 @@ class CGroupConfigurator(object):
 
             return self._invoke_cgroup_operation(__impl, "Failed to list the processes in the agent's cgroup.", on_error=__on_error)
 
-        def start_extension_command(self, extension_name, command, timeout, shell, cwd, env, stdout, stderr, # pylint: disable=R0913
-                                    error_code=ExtensionErrorCodes.PluginUnknownFailure):
+        # too-many-arguments<R0913> Disabled: argument list mimics Popen's
+        def start_extension_command(self, extension_name, command, timeout, shell, cwd, env, stdout, stderr, error_code=ExtensionErrorCodes.PluginUnknownFailure):  # pylint: disable=R0913
             """
             Starts a command (install/enable/etc) for an extension and adds the command's PID to the extension's cgroup
             :param extension_name: The extension executing the command
@@ -257,7 +265,8 @@ class CGroupConfigurator(object):
             :param error_code: Extension error code to raise in case of error
             """
             if not self.enabled():
-                process = subprocess.Popen(command, # pylint: disable=W1509
+                # subprocess-popen-preexec-fn<W1509> Disabled: code is not multi-threaded
+                process = subprocess.Popen(command,  # pylint: disable=W1509
                                            shell=shell,
                                            cwd=cwd,
                                            env=env,
@@ -290,7 +299,7 @@ class CGroupConfigurator(object):
     @staticmethod
     def get_instance():
         if CGroupConfigurator._instance is None:
-            CGroupConfigurator._instance = CGroupConfigurator.__impl()
+            CGroupConfigurator._instance = CGroupConfigurator.__Impl()
         return CGroupConfigurator._instance
 
     @staticmethod
diff --git a/azurelinuxagent/ga/monitor.py b/azurelinuxagent/ga/monitor.py
index cca841a1..53a58459 100644
--- a/azurelinuxagent/ga/monitor.py
+++ b/azurelinuxagent/ga/monitor.py
@@ -70,11 +70,11 @@ class PollResourceUsageOperation(PeriodicOperation):
                     if not CGroupConfigurator.is_agent_process(command_line):
                         unexpected_processes.append(command_line)
 
-                if len(unexpected_processes) > 0: # pylint: disable=len-as-condition
+                if unexpected_processes:
                     unexpected_processes.sort()
                     processes_check_error = "The agent's cgroup includes unexpected processes: {0}".format(ustr(unexpected_processes))
-        except Exception as e: # pylint: disable=C0103
-            processes_check_error = "Failed to check the processes in the agent's cgroup: {0}".format(ustr(e))
+        except Exception as exception:
+            processes_check_error = "Failed to check the processes in the agent's cgroup: {0}".format(ustr(exception))
 
         # Report a small sample of errors
         if processes_check_error != self._last_error and self._error_count < 5:
@@ -181,11 +181,13 @@ class MonitorHandler(ThreadHandlerInterface): # pylint: disable=R0902
             ResetPeriodicLogMessagesOperation(),
             PeriodicOperation("collect_and_send_events", self.collect_and_send_events, self.EVENT_COLLECTION_PERIOD),
             ReportNetworkErrorsOperation(),
-            PollResourceUsageOperation(),
             PeriodicOperation("send_host_plugin_heartbeat", self.send_host_plugin_heartbeat, self.HOST_PLUGIN_HEARTBEAT_PERIOD),
             PeriodicOperation("send_imds_heartbeat", self.send_imds_heartbeat, self.IMDS_HEARTBEAT_PERIOD),
             ReportNetworkConfigurationChangesOperation(),
         ]
+        if CGroupConfigurator.get_instance().enabled():
+            self._periodic_operations.append(PollResourceUsageOperation())
+
         self.protocol = None
         self.protocol_util = None
         self.health_service = None
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 1d529668..224adddb 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -243,7 +243,7 @@ class UpdateHandler(object): # pylint: disable=R0902
         self.child_process = None
         return
 
-    def run(self, debug=False): # pylint: disable=R0912,R0914
+    def run(self, debug=False):  # pylint: disable=R0912,R0914
         """
         This is the main loop which watches for agent and extension updates.
         """
@@ -258,6 +258,7 @@ class UpdateHandler(object): # pylint: disable=R0902
             protocol = self.protocol_util.get_protocol()
             protocol.update_goal_state()
 
+            # Initialize the common parameters for telemetry events
             initialize_event_logger_vminfo_common_parameters(protocol)
 
             # Log OS-specific info.
@@ -267,6 +268,24 @@ class UpdateHandler(object): # pylint: disable=R0902
             logger.info(os_info_msg)
             add_event(AGENT_NAME, op=WALAEventOperation.OSInfo, message=os_info_msg)
 
+            #
+            # Perform initialization tasks
+            #
+            from azurelinuxagent.ga.exthandlers import get_exthandlers_handler, migrate_handler_state
+            exthandlers_handler = get_exthandlers_handler(protocol)
+            migrate_handler_state()
+
+            from azurelinuxagent.ga.remoteaccess import get_remote_access_handler
+            remote_access_handler = get_remote_access_handler(protocol)
+
+            self._ensure_no_orphans()
+            self._emit_restart_event()
+            self._emit_changes_in_default_configuration()
+            self._ensure_partition_assigned()
+            self._ensure_readonly_files()
+            self._ensure_cgroups_initialized()
+            self._ensure_extension_telemetry_state_configured_properly(protocol)
+
             # Get all thread handlers
             all_thread_handlers = [
                 get_monitor_handler(),
@@ -284,21 +303,6 @@ class UpdateHandler(object): # pylint: disable=R0902
             for thread_handler in all_thread_handlers:
                 thread_handler.run()
 
-            from azurelinuxagent.ga.exthandlers import get_exthandlers_handler, migrate_handler_state
-            exthandlers_handler = get_exthandlers_handler(protocol)
-            migrate_handler_state()
-
-            from azurelinuxagent.ga.remoteaccess import get_remote_access_handler
-            remote_access_handler = get_remote_access_handler(protocol)
-
-            self._ensure_no_orphans()
-            self._emit_restart_event()
-            self._emit_changes_in_default_configuration()
-            self._ensure_partition_assigned()
-            self._ensure_readonly_files()
-            self._ensure_cgroups_initialized()
-            self._ensure_extension_telemetry_state_configured_properly(protocol)
-
             goal_state_interval = conf.get_goal_state_period() if conf.get_extensions_enabled() else GOAL_STATE_INTERVAL_DISABLED
 
             while self.running:


Test patch:
diff --git a/tests/common/mock_cgroup_commands.py b/tests/common/mock_cgroup_commands.py
index baa9d0a2..3cf49ac3 100644
--- a/tests/common/mock_cgroup_commands.py
+++ b/tests/common/mock_cgroup_commands.py
@@ -28,7 +28,7 @@ from tests.tools import patch, data_dir
 #
 # The output comes from an Ubuntu 18 system
 #
-_default_commands = [ # pylint: disable=invalid-name
+__DEFAULT_COMMANDS = [
     (r"^systemctl --version$",
 '''systemd 237
 +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid
@@ -74,7 +74,7 @@ Directory /sys/fs/cgroup/cpu/system.slice/walinuxagent.service:
 '''),
 ]
 
-_default_files = [ # pylint: disable=invalid-name
+__DEFAULT_FILES = [
     (r"^/proc/self/cgroup$", os.path.join(data_dir, 'cgroups', 'proc_self_cgroup')),
     (r"^/proc/[0-9]+/cgroup$", os.path.join(data_dir, 'cgroups', 'proc_pid_cgroup')),
     (r"^/sys/fs/cgroup/unified/cgroup.controllers$", os.path.join(data_dir, 'cgroups', 'sys_fs_cgroup_unified_cgroup.controllers')),
@@ -86,6 +86,9 @@ def mock_cgroup_commands():
     original_read_file = fileutil.read_file
     original_path_exists = os.path.exists
 
+    def add_file(pattern, file_path):
+        patcher.files.insert(0, (pattern, file_path))
+
     def add_command(pattern, output):
         patcher.commands.insert(0, (pattern, output))
 
@@ -104,15 +107,15 @@ def mock_cgroup_commands():
         return original_popen(command, *args, **kwargs)
     
     def mock_read_file(filepath, **kwargs):
-        for file in patcher.files: # pylint: disable=redefined-builtin
-            match = re.match(file[0], filepath)
+        for item in patcher.files:
+            match = re.match(item[0], filepath)
             if match is not None:
-                filepath = file[1]
+                filepath = item[1]
         return original_read_file(filepath, **kwargs)
 
     def mock_path_exists(path):
-        for file in patcher.files: # pylint: disable=redefined-builtin
-            match = re.match(file[0], path)
+        for item in patcher.files:
+            match = re.match(item[0], path)
             if match is not None:
                 return True
         return original_path_exists(path)
@@ -122,8 +125,9 @@ def mock_cgroup_commands():
             with patch("azurelinuxagent.common.cgroupapi.fileutil.read_file", side_effect=mock_read_file):
                 with patch('azurelinuxagent.common.cgroupapi.CGroupsApi.cgroups_supported', return_value=True):
                     with patch('azurelinuxagent.common.cgroupapi.CGroupsApi.is_systemd', return_value=True):
-                        patcher.commands = _default_commands[:]
-                        patcher.files = _default_files[:]
+                        patcher.commands = __DEFAULT_COMMANDS[:]
+                        patcher.files = __DEFAULT_FILES[:]
+                        patcher.add_file = add_file
                         patcher.add_command = add_command
                         yield patcher
 
diff --git a/tests/common/test_cgroupconfigurator.py b/tests/common/test_cgroupconfigurator.py
index 3e5fa2d7..1f78a7b6 100644
--- a/tests/common/test_cgroupconfigurator.py
+++ b/tests/common/test_cgroupconfigurator.py
@@ -31,29 +31,105 @@ from tests.tools import AgentTestCase, patch, mock_sleep
 class CGroupConfiguratorSystemdTestCase(AgentTestCase):
     @classmethod
     def tearDownClass(cls):
-        CGroupConfigurator._instance = None # pylint: disable=protected-access
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._instance from unit test for CGroupConfigurator
+        CGroupConfigurator._instance = None  # pylint: disable=protected-access
         AgentTestCase.tearDownClass()
 
     @staticmethod
-    def _get_new_cgroup_configurator_instance(initialize=True):
-        CGroupConfigurator._instance = None # pylint: disable=protected-access
+    def _get_new_cgroup_configurator_instance(initialize=True, mock_commands=None, mock_files=None):
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._instance from unit test for CGroupConfigurator
+        CGroupConfigurator._instance = None  # pylint: disable=protected-access
         configurator = CGroupConfigurator.get_instance()
+        CGroupsTelemetry.reset()
         if initialize:
-            with mock_cgroup_commands():
+            with mock_cgroup_commands() as mocks:
+                if mock_files is not None:
+                    for item in mock_files:
+                        mocks.add_file(item[0], item[1])
+                if mock_commands is not None:
+                    for command in mock_commands:
+                        mocks.add_command(command[0], command[1])
                 configurator.initialize()
         return configurator
 
     def test_initialize_should_start_tracking_the_agent_cgroups(self):
-        CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance()
-
-        tracked = CGroupsTelemetry._tracked # pylint: disable=protected-access
+        configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance()
 
-        self.assertTrue(
-            any(cg for cg in tracked if cg.name == 'walinuxagent.service' and 'cpu' in cg.path),
-            "The Agent's CPU is not being tracked")
-        self.assertTrue(
-            any(cg for cg in tracked if cg.name == 'walinuxagent.service' and 'memory' in cg.path),
-            "The Agent's memory is not being tracked")
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._tracked from unit test for CGroupConfigurator
+        tracked = CGroupsTelemetry._tracked  # pylint: disable=protected-access
+
+        self.assertTrue(configurator.enabled(), "Cgroups should be enabled")
+        self.assertTrue(any(cg for cg in tracked if cg.name == 'walinuxagent.service' and 'cpu' in cg.path),
+            "The Agent's CPU is not being tracked. Tracked: {0}".format(tracked))
+        self.assertTrue(any(cg for cg in tracked if cg.name == 'walinuxagent.service' and 'memory' in cg.path),
+            "The Agent's memory is not being tracked. Tracked: {0}".format(tracked))
+
+    def test_initialize_should_start_tracking_other_controllers_when_one_is_not_present(self):
+        configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance(
+            mock_commands=[(r"^mount -t cgroup$",
+ '''cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
+ cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
+ cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
+ cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
+ cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
+ cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
+ cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
+ cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
+ cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
+ cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
+ cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
+ ''')])
+
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._tracked from unit test for CGroupConfigurator
+        tracked = CGroupsTelemetry._tracked  # pylint: disable=protected-access
+
+        self.assertTrue(configurator.enabled(), "Cgroups should be enabled")
+        self.assertFalse(any(cg for cg in tracked if cg.name == 'walinuxagent.service' and 'cpu' in cg.path),
+            "The Agent's CPU should not be tracked. Tracked: {0}".format(tracked))
+        self.assertTrue(any(cg for cg in tracked if cg.name == 'walinuxagent.service' and 'memory' in cg.path),
+            "The Agent's memory is not being tracked. Tracked: {0}".format(tracked))
+
+    def test_initialize_should_not_enable_cgroups_is_the_cpu_and_memory_controllers_are_not_present(self):
+        configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance(
+            mock_commands=[(r"^mount -t cgroup$",
+'''cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
+cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
+cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
+cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
+cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
+cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
+cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
+cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
+cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
+cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
+                            ''')])
+
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._tracked from unit test for CGroupConfigurator
+        tracked = CGroupsTelemetry._tracked  # pylint: disable=protected-access
+
+        self.assertFalse(configurator.enabled(), "Cgroups should not be enabled")
+        self.assertEqual(len(tracked), 0, "No cgroups should be tracked. Tracked: {0}".format(tracked))
+
+    def test_initialize_should_not_enable_cgroups_when_the_agent_is_not_in_the_system_slice(self):
+        configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance(
+            mock_commands=[(r"^mount -t cgroup$",
+                            '''cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
+                            cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
+                            cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
+                            cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
+                            cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
+                            cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
+                            cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
+                            cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
+                            cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
+                            cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
+                                                        ''')])
+
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._tracked from unit test for CGroupConfigurator
+        tracked = CGroupsTelemetry._tracked  # pylint: disable=protected-access
+
+        self.assertFalse(configurator.enabled(), "Cgroups should not be enabled")
+        self.assertEqual(len(tracked), 0, "No cgroups should be tracked. Tracked: {0}".format(tracked))
 
     def test_enable_and_disable_should_change_the_enabled_state_of_cgroups(self):
         configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance()
@@ -66,7 +142,7 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
         configurator.enable()
         self.assertTrue(configurator.enabled(), "enable() should enable the CGroupConfigurator")
 
-    def test_enable_should_raise_CGroupsException_when_cgroups_are_not_supported(self): # pylint: disable=invalid-name
+    def test_enable_should_raise_cgroups_exception_when_cgroups_are_not_supported(self):
         with patch("azurelinuxagent.common.cgroupapi.CGroupsApi.cgroups_supported", return_value=False):
             configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance(initialize=False)
             configurator.initialize()
@@ -82,7 +158,8 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
 
         CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance().disable()
 
-        self.assertEqual(len(CGroupsTelemetry._tracked), 0) # pylint: disable=protected-access
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._tracked from unit test for CGroupConfigurator
+        self.assertEqual(len(CGroupsTelemetry._tracked), 0)  # pylint: disable=protected-access
 
     def test_cgroup_operations_should_not_invoke_the_cgroup_api_when_cgroups_are_not_enabled(self):
         configurator = CGroupConfiguratorSystemdTestCase._get_new_cgroup_configurator_instance()
@@ -90,14 +167,14 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
 
         # List of operations to test, and the functions to mock used in order to do verifications
         operations = [
-            [lambda: configurator.create_extension_cgroups_root(),           "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.create_extension_cgroups_root"], # pylint: disable=unnecessary-lambda
+            [configurator.create_extension_cgroups_root,                     "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.create_extension_cgroups_root"],
             [lambda: configurator.create_extension_cgroups("A.B.C-1.0.0"),   "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.create_extension_cgroups"],
             [lambda: configurator.remove_extension_cgroups("A.B.C-1.0.0"),   "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.remove_extension_cgroups"]
         ]
 
-        for op in operations: # pylint: disable=invalid-name
-            with patch(op[1]) as mock_cgroup_api_operation:
-                op[0]()
+        for operation in operations:
+            with patch(operation[1]) as mock_cgroup_api_operation:
+                operation[0]()
 
             self.assertEqual(mock_cgroup_api_operation.call_count, 0)
 
@@ -108,7 +185,7 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
         with patch.object(configurator, "disable"):
             # List of operations to test, and the functions to mock in order to raise exceptions
             operations = [
-                [lambda: configurator.create_extension_cgroups_root(),           "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.create_extension_cgroups_root"], # pylint: disable=unnecessary-lambda
+                [configurator.create_extension_cgroups_root,                     "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.create_extension_cgroups_root"],
                 [lambda: configurator.create_extension_cgroups("A.B.C-1.0.0"),   "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.create_extension_cgroups"],
                 [lambda: configurator.remove_extension_cgroups("A.B.C-1.0.0"),   "azurelinuxagent.common.cgroupapi.SystemdCgroupsApi.remove_extension_cgroups"]
             ]
@@ -116,14 +193,14 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
             def raise_exception(*_):
                 raise Exception("A TEST EXCEPTION")
 
-            for op in operations: # pylint: disable=invalid-name
+            for operation in operations:
                 with patch("azurelinuxagent.common.cgroupconfigurator.logger.warn") as mock_logger_warn:
-                    with patch(op[1], raise_exception):
-                        op[0]()
+                    with patch(operation[1], raise_exception):
+                        operation[0]()
 
                     self.assertEqual(mock_logger_warn.call_count, 1)
 
-                    args, kwargs = mock_logger_warn.call_args # pylint: disable=unused-variable
+                    args, _ = mock_logger_warn.call_args
                     message = args[0]
                     self.assertIn("A TEST EXCEPTION", message)
 
@@ -197,7 +274,8 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE)
 
-        tracked = CGroupsTelemetry._tracked # pylint: disable=protected-access
+        # protected-access<W0212> Disabled: OK to access CGroupConfigurator._tracked from unit test for CGroupConfigurator
+        tracked = CGroupsTelemetry._tracked  # pylint: disable=protected-access
 
         self.assertTrue(
             any(cg for cg in tracked if cg.name == 'Microsoft.Compute.TestExtension-1.2.3' and 'cpu' in cg.path),
@@ -216,7 +294,7 @@ class CGroupConfiguratorSystemdTestCase(AgentTestCase):
                 raise Exception("A TEST EXCEPTION")
             return original_popen(command_arg, *args, **kwargs)
 
-        with patch("azurelinuxagent.common.cgroupapi.subprocess.Popen", side_effect=mock_popen) as patcher: # pylint: disable=unused-variable
+        with patch("azurelinuxagent.common.cgroupapi.subprocess.Popen", side_effect=mock_popen):
             with self.assertRaises(Exception) as context_manager:
                 configurator.start_extension_command(
                     extension_name="Microsoft.Compute.TestExtension-1.2.3",