def test_read_output_it_should_truncate_the_content(self): with patch( 'azurelinuxagent.common.utils.processutil.TELEMETRY_MESSAGE_MAX_LEN', 10): expected = "[stdout]\nThe quick \n\n[stderr]\nThe five b" actual = read_output(self.stdout, self.stderr) self.assertEqual(expected, actual)
def test_save_event_message_with_non_ascii_characters(self): test_data_dir = os.path.join( data_dir, "events", "collect_and_send_extension_stdout_stderror") msg = "" with open(os.path.join(test_data_dir, "dummy_stdout_with_non_ascii_characters"), mode="r+b") as stdout: with open(os.path.join(test_data_dir, "dummy_stderr_with_non_ascii_characters"), mode="r+b") as stderr: msg = read_output(stdout, stderr) duration = elapsed_milliseconds(datetime.utcnow()) log_msg = "{0}\n{1}".format( "DummyCmd", "\n".join([line for line in msg.split('\n') if line != ""])) add_event('test_extension', message=log_msg, duration=duration) for tld_file in os.listdir(self.tmp_dir): event_str = MonitorHandler.collect_event( os.path.join(self.tmp_dir, tld_file)) event_json = json.loads(event_str) self.assertEqual(len(event_json["parameters"]), 8) for i in event_json["parameters"]: if i["name"] == "Name": self.assertEqual(i["value"], "test_extension") if i["name"] == "Message": self.assertEqual(i["value"], log_msg)
def test_read_output_it_should_return_no_content(self): with patch( 'azurelinuxagent.common.utils.processutil.TELEMETRY_MESSAGE_MAX_LEN', 0): expected = "[stdout]\n\n\n[stderr]\n" actual = read_output(self.stdout, self.stderr) self.assertEqual(expected, actual)
def test_read_output_it_should_return_all_content(self): with patch( 'azurelinuxagent.common.utils.processutil.TELEMETRY_MESSAGE_MAX_LEN', 50): expected = "[stdout]\nThe quick brown fox jumps over the lazy dog.\n\n" \ "[stderr]\nThe five boxing wizards jump quickly." actual = read_output(self.stdout, self.stderr) self.assertEqual(expected, actual)
def test_start_extension_command_should_capture_only_the_last_subprocess_output( self): original_popen = subprocess.Popen def mock_popen(*args, **kwargs): # Inject a syntax error to the call systemd_command = args[0].replace('systemd-run', 'systemd-run syntax_error') new_args = (systemd_command, ) return original_popen(new_args, **kwargs) with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stdout: with tempfile.TemporaryFile(dir=self.tmp_dir, mode="w+b") as stderr: with patch("azurelinuxagent.common.cgroupapi.add_event" ) as mock_add_event: with patch( "azurelinuxagent.common.cgroupapi.subprocess.Popen", side_effect=mock_popen) as patch_mock_popen: # We expect this call to fail because of the syntax error process, extension_cgroups = SystemdCgroupsApi( ).start_extension_command( extension_name= "Microsoft.Compute.TestExtension-1.2.3", command="echo 'very specific test message'", shell=True, cwd=self.tmp_dir, env={}, stdout=stdout, stderr=stderr) process.wait() process_output = read_output(stdout, stderr) self.assertEquals( "[stdout]\nvery specific test message\n\n\n[stderr]\n", process_output)
def test_read_output_it_should_handle_exceptions(self): with patch( 'azurelinuxagent.common.utils.processutil.TELEMETRY_MESSAGE_MAX_LEN', "type error"): actual = read_output(self.stdout, self.stderr) self.assertIn("Cannot read stdout/stderr", actual)
def start_extension_command(self, extension_name, command, shell, cwd, env, stdout, stderr): scope_name = "{0}_{1}".format( self._get_extension_cgroup_name(extension_name), uuid.uuid4()) process = subprocess.Popen("systemd-run --unit={0} --scope {1}".format( scope_name, command), shell=shell, cwd=cwd, stdout=stdout, stderr=stderr, env=env, preexec_fn=os.setsid) # Wait a bit and check if we completed with error time.sleep(1) return_code = process.poll() if return_code is not None and return_code != 0: process_output = read_output(stdout, stderr) # When systemd-run successfully invokes a command, thereby creating its unit, it will output the # unit's name. Since the scope name is only known to systemd-run, and not to the extension itself, # if scope_name appears in the output, we are certain systemd-run managed to run. if scope_name not in process_output: logger.warn( 'Failed to run systemd-run for unit {0}.scope ' 'Process exited with code {1} and output {2}'.format( scope_name, return_code, process_output)) add_event( AGENT_NAME, version=CURRENT_VERSION, op=WALAEventOperation.InvokeCommandUsingSystemd, is_success=False, message='Failed to run systemd-run for unit {0}.scope. ' 'Process exited with code {1} and output {2}'.format( scope_name, return_code, process_output)) # Try starting the process without systemd-run process = subprocess.Popen(command, shell=shell, cwd=cwd, env=env, stdout=stdout, stderr=stderr, preexec_fn=os.setsid) return process, [] cgroups = [] logger.info("Started extension using scope '{0}'", scope_name) def create_cgroup(controller): cgroup_path = os.path.join(CGROUPS_FILE_SYSTEM_ROOT, controller, 'system.slice', scope_name + ".scope") cgroups.append( CGroup.create(cgroup_path, controller, extension_name)) self._foreach_controller( create_cgroup, 'Cannot create cgroup for extension {0}; resource usage will not be tracked.' .format(extension_name)) return process, cgroups