def test_mount_cgroups_should_handle_errors_when_mounting_an_individual_controller( self): original_run_get_output = shellutil.run_get_output def mock_run_get_output(cmd, *args, **kwargs): if cmd.startswith('mount '): if 'memory' in cmd: raise Exception( 'A test exception mounting the memory controller') return 0, None return original_run_get_output(cmd, *args, **kwargs) with patch( "azurelinuxagent.common.osutil.default.shellutil.run_get_output", side_effect=mock_run_get_output) as patch_run_get_output: with patch("azurelinuxagent.common.cgroupconfigurator.logger.warn" ) as mock_logger_warn: FileSystemCgroupsApi.mount_cgroups() # the cgroup filesystem and the cpu controller should still have been mounted mount_commands = MountCgroupsTestCase._get_mount_commands( patch_run_get_output) self.assertRegex(mount_commands, ';mount.* cgroup_root ', 'The cgroups file system was not mounted') self.assertRegex(mount_commands, ';mount.* cpu,cpuacct ', 'The cpu controller was not mounted') # A warning should have been logged for the memory controller args, kwargs = mock_logger_warn.call_args self.assertIn( 'A test exception mounting the memory controller', args)
def test_mount_cgroups_should_raise_when_the_cgroups_filesystem_fails_to_mount( self): original_run_get_output = shellutil.run_get_output def mock_run_get_output(cmd, *args, **kwargs): if cmd.startswith('mount '): if 'cgroup_root' in cmd: raise Exception( 'A test exception mounting the cgroups file system') return 0, None return original_run_get_output(cmd, *args, **kwargs) with patch( "azurelinuxagent.common.osutil.default.shellutil.run_get_output", side_effect=mock_run_get_output) as patch_run_get_output: with self.assertRaises(Exception) as context_manager: FileSystemCgroupsApi.mount_cgroups() self.assertRegex( str(context_manager.exception), 'A test exception mounting the cgroups file system') mount_commands = MountCgroupsTestCase._get_mount_commands( patch_run_get_output) self.assertNotIn( 'memory', mount_commands, 'The memory controller should not have been mounted') self.assertNotIn( 'cpu', mount_commands, 'The cpu controller should not have been mounted')
def test_mount_cgroups_should_mount_the_cpu_and_memory_controllers(self): # the mount command requires root privileges; make it a no op and check only for file existence original_run_get_output = shellutil.run_get_output def mock_run_get_output(cmd, *args, **kwargs): if cmd.startswith('mount '): return 0, None return original_run_get_output(cmd, *args, **kwargs) with patch( "azurelinuxagent.common.osutil.default.shellutil.run_get_output", side_effect=mock_run_get_output) as patch_run_get_output: FileSystemCgroupsApi.mount_cgroups() # the directories for the controllers should have been created for controller in ['cpu', 'memory', 'cpuacct', 'cpu,cpuacct']: directory = os.path.join(self.cgroups_file_system_root, controller) self.assertTrue( os.path.exists(directory), "A directory for controller {0} was not created".format( controller)) # the cgroup filesystem and the cpu and memory controllers should have been mounted mount_commands = MountCgroupsTestCase._get_mount_commands( patch_run_get_output) self.assertRegex(mount_commands, ';mount.* cgroup_root ', 'The cgroups file system was not mounted') self.assertRegex(mount_commands, ';mount.* cpu,cpuacct ', 'The cpu controller was not mounted') self.assertRegex(mount_commands, ';mount.* memory ', 'The memory controller was not mounted')
def test_mount_cgroups_should_not_mount_cgroup_controllers_when_they_already_exist( self): os.mkdir(self.cgroups_file_system_root) os.mkdir(os.path.join(self.cgroups_file_system_root, 'cpu,cpuacct')) os.mkdir(os.path.join(self.cgroups_file_system_root, 'memory')) original_run_get_output = shellutil.run_get_output def mock_run_get_output(cmd, *args, **kwargs): if cmd.startswith('mount '): return 0, None return original_run_get_output(cmd, *args, **kwargs) with patch( "azurelinuxagent.common.osutil.default.shellutil.run_get_output", side_effect=mock_run_get_output) as patch_run_get_output: FileSystemCgroupsApi.mount_cgroups() mount_commands = MountCgroupsTestCase._get_mount_commands( patch_run_get_output) self.assertNotIn( 'cgroup_root', mount_commands, 'The cgroups file system should not have been mounted') self.assertNotIn( 'cpu,cpuacct', mount_commands, 'The cpu controller should not have been mounted') self.assertNotIn( 'memory', mount_commands, 'The memory controller should not have been mounted')
def test_start_extension_command_should_add_the_child_process_to_the_extension_cgroup( self): api = FileSystemCgroupsApi() api.create_extension_cgroups_root() process, extension_cgroups = api.start_extension_command( extension_name="Microsoft.Compute.TestExtension-1.2.3", command="date", shell=False, cwd=self.tmp_dir, env={}, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.wait() for cgroup in extension_cgroups: cgroups_procs_path = os.path.join(cgroup.path, "cgroup.procs") with open(cgroups_procs_path, "r") as f: contents = f.read() pid = int(contents) self.assertEquals( pid, process.pid, "The PID of the command was not added to {0}. Expected: {1}, got: {2}" .format(cgroups_procs_path, process.pid, pid))
def test_create_extension_cgroups_should_create_cgroups_on_all_controllers(self): api = FileSystemCgroupsApi() api.create_extension_cgroups_root() extension_cgroups = api.create_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") def assert_cgroup_created(controller): cgroup_path = os.path.join(self.cgroups_file_system_root, controller, "walinuxagent.extensions", "Microsoft.Compute.TestExtension_1.2.3") self.assertTrue(any(cgroups.path == cgroup_path for cgroups in extension_cgroups)) self.assertTrue(os.path.exists(cgroup_path)) assert_cgroup_created("cpu") assert_cgroup_created("memory")
def test_create_extension_cgroups_root_should_create_root_directory_for_extensions(self): FileSystemCgroupsApi().create_extension_cgroups_root() cpu_cgroup = os.path.join(self.cgroups_file_system_root, "cpu", "walinuxagent.extensions") self.assertTrue(os.path.exists(cpu_cgroup)) memory_cgroup = os.path.join(self.cgroups_file_system_root, "memory", "walinuxagent.extensions") self.assertTrue(os.path.exists(memory_cgroup))
def test_cleanup_legacy_cgroups_should_move_daemon_pid_to_new_cgroup_and_remove_legacy_cgroups( self): # Set up a mock /var/run/waagent.pid file daemon_pid = "42" daemon_pid_file = os.path.join(self.tmp_dir, "waagent.pid") fileutil.write_file(daemon_pid_file, daemon_pid + "\n") # Set up old controller cgroups and add the daemon PID to them legacy_cpu_cgroup = CGroupsTools.create_legacy_agent_cgroup( self.cgroups_file_system_root, "cpu", daemon_pid) legacy_memory_cgroup = CGroupsTools.create_legacy_agent_cgroup( self.cgroups_file_system_root, "memory", daemon_pid) # Set up new controller cgroups and add extension handler's PID to them new_cpu_cgroup = CGroupsTools.create_agent_cgroup( self.cgroups_file_system_root, "cpu", "999") new_memory_cgroup = CGroupsTools.create_agent_cgroup( self.cgroups_file_system_root, "memory", "999") with patch("azurelinuxagent.common.cgroupapi.add_event" ) as mock_add_event: with patch( "azurelinuxagent.common.cgroupapi.get_agent_pid_file_path", return_value=daemon_pid_file): FileSystemCgroupsApi().cleanup_legacy_cgroups() # The method should have added the daemon PID to the new controllers and deleted the old ones new_cpu_contents = fileutil.read_file( os.path.join(new_cpu_cgroup, "cgroup.procs")) new_memory_contents = fileutil.read_file( os.path.join(new_memory_cgroup, "cgroup.procs")) self.assertTrue(daemon_pid in new_cpu_contents) self.assertTrue(daemon_pid in new_memory_contents) self.assertFalse(os.path.exists(legacy_cpu_cgroup)) self.assertFalse(os.path.exists(legacy_memory_cgroup)) # Assert the event parameters that were sent out self.assertEquals(len(mock_add_event.call_args_list), 2) self.assertTrue( all(kwargs['op'] == 'CGroupsCleanUp' for _, kwargs in mock_add_event.call_args_list)) self.assertTrue( all(kwargs['is_success'] for _, kwargs in mock_add_event.call_args_list)) self.assertTrue( any( re.match( r"Moved daemon's PID from legacy cgroup to /.*/cgroup/cpu/walinuxagent.service", kwargs['message']) for _, kwargs in mock_add_event.call_args_list)) self.assertTrue( any( re.match( r"Moved daemon's PID from legacy cgroup to /.*/cgroup/memory/walinuxagent.service", kwargs['message']) for _, kwargs in mock_add_event.call_args_list))
def test_remove_extension_cgroups_should_log_a_warning_when_the_cgroup_contains_active_tasks(self): api = FileSystemCgroupsApi() api.create_extension_cgroups_root() api.create_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") with patch("azurelinuxagent.common.cgroupapi.logger.warn") as mock_logger_warn: with patch("azurelinuxagent.common.cgroupapi.os.rmdir", side_effect=OSError(16, "Device or resource busy")): api.remove_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") args, kwargs = mock_logger_warn.call_args message = args[0] self.assertIn("still has active tasks", message)
def test_remove_extension_cgroups_should_remove_all_cgroups(self): api = FileSystemCgroupsApi() api.create_extension_cgroups_root() extension_cgroups = api.create_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") api.remove_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") for cgroup in extension_cgroups: self.assertFalse(os.path.exists(cgroup.path))
def test_mount_cgroups_should_raise_when_all_controllers_fail_to_mount( self): original_run_get_output = shellutil.run_get_output def mock_run_get_output(cmd, *args, **kwargs): if cmd.startswith('mount '): if 'memory' in cmd or 'cpu,cpuacct' in cmd: raise Exception( 'A test exception mounting a cgroup controller') return 0, None return original_run_get_output(cmd, *args, **kwargs) with patch( "azurelinuxagent.common.osutil.default.shellutil.run_get_output", side_effect=mock_run_get_output): with self.assertRaises(Exception) as context_manager: FileSystemCgroupsApi.mount_cgroups() self.assertRegex(str(context_manager.exception), 'A test exception mounting a cgroup controller')
def test_start_extension_command_should_add_the_child_process_to_the_extension_cgroup( self, _): api = FileSystemCgroupsApi() api.create_extension_cgroups_root() with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stdout: with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stderr: extension_cgroups, process_output = api.start_extension_command( extension_name="Microsoft.Compute.TestExtension-1.2.3", command="echo $$", timeout=300, shell=True, cwd=self.tmp_dir, env={}, stdout=stdout, stderr=stderr) # The expected format of the process output is [stdout]\n{PID}\n\n\n[stderr]\n" pattern = re.compile(r"\[stdout\]\n(\d+)\n\n\n\[stderr\]\n") m = pattern.match(process_output) try: pid_from_output = int(m.group(1)) except Exception as e: self.fail( "No PID could be extracted from the process output! Error: {0}" .format(ustr(e))) for cgroup in extension_cgroups: cgroups_procs_path = os.path.join(cgroup.path, "cgroup.procs") with open(cgroups_procs_path, "r") as f: contents = f.read() pid_from_cgroup = int(contents) self.assertEquals( pid_from_output, pid_from_cgroup, "The PID from the process output ({0}) does not match the PID found in the" "process cgroup {1} ({2})".format(pid_from_output, cgroups_procs_path, pid_from_cgroup))
def test_mount_cgroups_should_not_create_symbolic_links_when_the_cpu_controller_fails_to_mount( self): original_run_get_output = shellutil.run_get_output def mock_run_get_output(cmd, *args, **kwargs): if cmd.startswith('mount '): if 'cpu,cpuacct' in cmd: raise Exception( 'A test exception mounting the cpu controller') return 0, None return original_run_get_output(cmd, *args, **kwargs) with patch( "azurelinuxagent.common.osutil.default.shellutil.run_get_output", side_effect=mock_run_get_output): with patch("azurelinuxagent.common.osutil.default.os.symlink" ) as patch_symlink: FileSystemCgroupsApi.mount_cgroups() self.assertEquals( patch_symlink.call_count, 0, 'A symbolic link should not have been created')
def test_create_agent_cgroups_should_create_cgroups_on_all_controllers(self): agent_cgroups = FileSystemCgroupsApi().create_agent_cgroups() def assert_cgroup_created(controller): cgroup_path = os.path.join(self.cgroups_file_system_root, controller, "walinuxagent.service") self.assertTrue(any(cgroups.path == cgroup_path for cgroups in agent_cgroups)) self.assertTrue(os.path.exists(cgroup_path)) cgroup_task = int(fileutil.read_file(os.path.join(cgroup_path, "cgroup.procs"))) current_process = os.getpid() self.assertEqual(cgroup_task, current_process) assert_cgroup_created("cpu") assert_cgroup_created("memory")
def test_get_extension_cgroups_should_return_all_cgroups(self): api = FileSystemCgroupsApi() api.create_extension_cgroups_root() created = api.create_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") retrieved = api.get_extension_cgroups("Microsoft.Compute.TestExtension-1.2.3") self.assertEqual(len(retrieved), len(created)) for cgroup in created: self.assertTrue(any(retrieved_cgroup.path == cgroup.path for retrieved_cgroup in retrieved))