"""Various utility methods over runner objects"""
import ntpath
import os
import posixpath
import subprocess
import threading

from parallels.core import messages, MigrationError
from parallels.core import popen_no_inherit
from parallels.core.logging import get_logger
from parallels.core.runners.windows.agent import WindowsAgentRunner
from parallels.core.runners.windows.local import LocalWindowsRunner
from parallels.core.utils import windows_utils
from parallels.core.utils.clean_logging import clean_dict_args_for_log
from parallels.core.utils.common import unused, open_no_inherit
from parallels.core.utils.entity import Entity
from plesk_migrator.compatibility.utils import stringio

# Default chunk size for read/write operations - 1MB
DEFAULT_CHUNK_SIZE = 1024 * 1024
TRUNCATED_STDERR_LENGTH = 10240

logger = get_logger(__name__)


def transfer_file(source_runner, source_filename, target_runner, target_filename):
    """Transfer file between 2 runner objects

    Now supports transfer only for WindowsAgentRunner (RPC agent) and LocalWindowsRunner

    :type source_runner: parallels.core.runners.base.BaseRunner
    :type target_runner: parallels.core.runners.base.BaseRunner
    :type source_filename: str | unicode
    :type target_filename: str | unicode
    """
    if isinstance(source_runner, WindowsAgentRunner) and isinstance(target_runner, WindowsAgentRunner):
        chunk_size = DEFAULT_CHUNK_SIZE
        target_runner.remote.create_file(target_filename)
        offset = 0
        while True:
            data = source_runner.remote.get_file_chunk(source_filename, offset, chunk_size)
            target_runner.remote.append_file(target_filename, data)
            offset += len(data)
            if len(data) < chunk_size:
                break
    elif isinstance(source_runner, WindowsAgentRunner) and isinstance(target_runner, LocalWindowsRunner):
        source_runner.get_file(source_filename, target_filename)
    elif isinstance(source_runner, LocalWindowsRunner) and isinstance(target_runner, WindowsAgentRunner):
        target_runner.upload_file(source_filename, target_filename)
    else:
        raise NotImplementedError()


def transfer_local_dir(local_dir, remote_dir, runner, is_windows):
    """Transfer local directory to remote server

    :type local_dir: str | unicode
    :type remote_dir: str | unicode
    :type runner: parallels.core.runners.base.BaseRunner
    :type is_windows: bool
    :rtype: None
    """
    for file_name in os.listdir(local_dir):
        local_path = os.path.join(local_dir, file_name)
        remote_path = (ntpath if is_windows else posixpath).join(
            remote_dir, file_name
        )
        if os.path.isfile(local_path):
            runner.upload_file(local_path, remote_path)
        else:
            runner.mkdir(remote_path)
            transfer_local_dir(local_path, remote_path, runner, is_windows)


def pipe_commands(source_runner, source_command, source_args, target_runner, target_command, target_args):
    """Run 2 commands on 2 servers, connecting stdout of one of them to stdin of the other one

    "Source" means server from which we read stdout.
    "Target" means server for which we write data to stdin.

    '*_command' arguments are a format strings
    '*_args' arguments are dictionaries with data to be substituted into format string,
      in the same way that is done when using "sh" and "sh_unchecked" methods of runner.

    Implemented only for 2 cases:
    1) piping from RPC agent runner to Windows local runner.
    2) piping from RPC agent runner to another RPC agent runner.

    :type source_runner: parallels.core.runners.base.BaseRunner
    :type source_command: str | unicode
    :type source_args: dict | None
    :type target_runner: parallels.core.runners.base.BaseRunner
    :type target_command: str | unicode
    :type target_args: dict | None
    :rtype: parallels.core.runners.utils.PipeCommandsResult
    """
    if isinstance(source_runner, WindowsAgentRunner) and isinstance(target_runner, LocalWindowsRunner):
        return pipe_commands_remote_to_local(
            source_runner, source_command, source_args, target_command, target_args
        )
    elif isinstance(source_runner, WindowsAgentRunner) and isinstance(target_runner, WindowsAgentRunner):
        return pipe_commands_remote_to_remote(
            source_runner, source_command, source_args, target_runner, target_command, target_args
        )
    else:
        raise NotImplementedError()


def pipe_commands_local_to_local(
    source_command, source_args, target_command, target_args,
    read_chunk_size=DEFAULT_CHUNK_SIZE
):
    """Run 2 commands on a local server, connecting stdout of the 1st command to stdin of the 2nd one

    "Source" means command for which we read from stdout.
    "Target" means command for which we write to stdin.

    '*_command' arguments are format strings
    '*_args' arguments are dictionaries with data to be substituted into format string,
      in the same way that is done when using "sh" and "sh_unchecked" methods of runner.

    :type source_command: str | unicode
    :type source_args: dict | None
    :type target_command: str | unicode
    :type target_args: dict | None
    :type read_chunk_size: int
    :rtype: parallels.core.runners.utils.PipeCommandsResult
    """
    log_target_command_str = windows_utils.format_command_dict(
        target_command, clean_dict_args_for_log(target_args)
    )
    log_source_command_str = windows_utils.format_command_dict(
        source_command, clean_dict_args_for_log(source_args)
    )
    logger.fdebug(
        messages.PIPE_COMMAND_LOCAL_TO_LOCAL_START,
        command_source=log_source_command_str, command_target=log_target_command_str
    )

    target_command_str = windows_utils.format_command_dict(target_command, target_args)
    source_command_str = windows_utils.format_command_dict(source_command, source_args)

    target_command_env = os.environ.copy()
    # always execute commands with en_US locale, so we could rely on their output
    target_command_env['LANG'] = 'en_US.utf-8'

    target_popen_handle = popen_no_inherit(
        target_command_str,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=target_command_env,
    )

    source_command_env = os.environ.copy()
    # always execute commands with en_US locale, so we could rely on their output
    source_command_env['LANG'] = 'en_US.utf-8'

    source_popen_handle = popen_no_inherit(
        source_command_str,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=source_command_env,
    )

    source_stderr_data = stringio(is_unicode=False)
    target_stderr_data = stringio(is_unicode=False)

    result = PipeCommandsResult()

    source_popen_handle.stdin.close()

    def consume_source_stdout():
        # Thread #1: Read data from source STDOUT, write data to target STDIN
        while source_popen_handle.returncode is None:
            source_popen_handle.poll()
            data = source_popen_handle.stdout.read(read_chunk_size)
            if len(data) > 0:
                target_popen_handle.stdin.write(data)
                target_popen_handle.stdin.flush()

        data = source_popen_handle.stdout.read()
        if len(data) > 0:
            target_popen_handle.stdin.write(data)
            target_popen_handle.stdin.flush()

        target_popen_handle.stdin.close()

    def consume_source_stderr():
        # Thread #2: Read data from source STDERR, write it to variable
        while source_popen_handle.returncode is None:
            source_popen_handle.poll()
            data = source_popen_handle.stderr.read(read_chunk_size)
            if len(data) > 0:
                source_stderr_data.write(data)

        data = source_popen_handle.stderr.read()
        if len(data) > 0:
            source_stderr_data.write(data)

    def consume_target_stdout():
        # Thread #3: Ignore target STDOUT
        while target_popen_handle.returncode is None:
            target_popen_handle.poll()
            target_popen_handle.stdout.read(read_chunk_size)

        target_popen_handle.stdout.read()

    def consume_target_stderr():
        # Thread #4: Read data from target STDERR, write it to variable
        while target_popen_handle.returncode is None:
            target_popen_handle.poll()
            stderr_data = target_popen_handle.stderr.read(read_chunk_size)
            if len(stderr_data) > 0:
                target_stderr_data.write(stderr_data)

        stderr_data = target_popen_handle.stderr.read()
        if len(stderr_data) > 0:
            target_stderr_data.write(stderr_data)

    thread_stdout_target = threading.Thread(target=consume_target_stdout)
    thread_stdout_target.start()
    thread_stderr_target = threading.Thread(target=consume_target_stderr)
    thread_stderr_target.start()
    thread_stdout_source = threading.Thread(target=consume_source_stdout)
    thread_stdout_source.start()
    thread_stderr_source = threading.Thread(target=consume_source_stderr)
    thread_stderr_source.start()

    thread_stdout_target.join()
    thread_stderr_target.join()
    thread_stdout_source.join()
    thread_stderr_source.join()

    result.target_exit_code = target_popen_handle.returncode
    result.source_exit_code = source_popen_handle.returncode

    result.source_stderr = source_stderr_data.getvalue()
    source_stderr_data.close()
    result.target_stderr = target_stderr_data.getvalue()
    target_stderr_data.close()

    try:
        logger.fdebug(
            messages.PIPE_COMMAND_LOCAL_TO_LOCAL_FINISH,
            exit_code_target=result.target_exit_code,
            exit_code_source=result.source_exit_code,
            stderr_target=result.target_stderr,
            stderr_source=result.source_stderr,
        )
    except MemoryError:
        logger.fdebug(messages.FAILED_TO_LOG_PIPE_COMMAND_RESULTS)
        logger.exception()
        logger.fdebug(
            messages.PIPE_COMMAND_LOCAL_TO_LOCAL_FINISH_TRUNCATED,
            exit_code_target=result.target_exit_code,
            exit_code_source=result.source_exit_code,
            stderr_target=result.target_stderr[-TRUNCATED_STDERR_LENGTH:],
            stderr_source=result.source_stderr[-TRUNCATED_STDERR_LENGTH:],
        )

    return result


def pipe_commands_remote_to_local(
    source_runner, source_command, source_args, target_command, target_args
):
    """Run 2 commands on 2 servers (remote and local), connecting stdout of remote server to stdin of local one

    "Source" means server from which we read stdout - remote server with RPC agent.
    "Target" means server for which we write data to stdin - local server.

    '*_command' arguments are a format strings
    '*_args' arguments are dictionaries with data to be substituted into format string,
      in the same way that is done when using "sh" and "sh_unchecked" methods of runner.

    :type source_runner: parallels.core.runners.windows.agent.WindowsAgentRunner
    :type source_command: str | unicode
    :type source_args: dict | None
    :type target_command: str | unicode
    :type target_args: dict | None
    :rtype: parallels.core.runners.utils.PipeCommandsResult
    """
    log_target_command_str = windows_utils.format_command_dict(
        target_command, clean_dict_args_for_log(target_args)
    )
    log_source_command_str = windows_utils.format_command_dict(
        source_command, clean_dict_args_for_log(source_args)
    )
    logger.fdebug(
        messages.PIPE_COMMAND_REMOTE_TO_LOCAL_START,
        remote_server=source_runner.settings.ip,
        command_remote=log_source_command_str, command_local=log_target_command_str
    )

    target_command_str = windows_utils.format_command_dict(target_command, target_args)

    target_command_env = os.environ.copy()
    # always execute commands with en_US locale, so we could rely on their output
    target_command_env['LANG'] = 'en_US.utf-8'

    popen_handle = popen_no_inherit(
        target_command_str,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        env=target_command_env,
    )

    source_stderr_data = stringio(is_unicode=False)
    target_stderr_data = stringio(is_unicode=False)

    result = PipeCommandsResult()

    def consume_source_stdout(data):
        popen_handle.stdin.write(data)

    def consume_source_stderr(data):
        source_stderr_data.write(data)

    def error_source(error_message):
        popen_handle.stdin.close()
        result.source_error = error_message

    def finalize_source(return_code):
        popen_handle.stdin.close()
        result.source_exit_code = return_code

    def consume_target_stdout():
        while popen_handle.returncode is None:
            popen_handle.poll()
            popen_handle.stdout.read(DEFAULT_CHUNK_SIZE)

    def consume_target_stderr():
        while popen_handle.returncode is None:
            popen_handle.poll()
            stderr_data = popen_handle.stderr.read(DEFAULT_CHUNK_SIZE)
            if len(stderr_data) > 0:
                target_stderr_data.write(stderr_data)

    thread_stdout = threading.Thread(target=consume_target_stdout)
    thread_stdout.start()
    thread_stderr = threading.Thread(target=consume_target_stderr)
    thread_stderr.start()

    source_runner.piped_sh_unchecked(
        source_command, source_args,
        consume_source_stdout, consume_source_stderr,
        finalize_source, error_source
    )
    thread_stdout.join()
    thread_stderr.join()

    stderr_last_data = popen_handle.stderr.read()
    if len(stderr_last_data) > 0:
        target_stderr_data.write(stderr_last_data)

    result.target_exit_code = popen_handle.returncode

    result.source_stderr = source_stderr_data.getvalue()
    source_stderr_data.close()
    result.target_stderr = target_stderr_data.getvalue()
    target_stderr_data.close()

    try:
        logger.fdebug(
            messages.PIPE_COMMAND_REMOTE_TO_LOCAL_FINISH,
            exit_code_local=result.target_exit_code,
            exit_code_remote=result.source_exit_code,
            stderr_local=result.target_stderr,
            stderr_remote=result.source_stderr,
            error_remote=result.source_error
        )
    except MemoryError:
        logger.fdebug(messages.FAILED_TO_LOG_PIPE_COMMAND_RESULTS)
        logger.exception()
        logger.fdebug(
            messages.PIPE_COMMAND_REMOTE_TO_LOCAL_FINISH_TRUNCATED,
            exit_code_local=result.target_exit_code,
            exit_code_remote=result.source_exit_code,
            stderr_local=result.target_stderr[-TRUNCATED_STDERR_LENGTH:],
            stderr_remote=result.source_stderr[-TRUNCATED_STDERR_LENGTH:],
            error_remote=result.source_error
        )

    return result


def pipe_commands_remote_to_remote(
    source_runner, source_command, source_args, target_runner, target_command, target_args
):
    """Run 2 commands on 2 remote servers, connecting stdout of one server to stdin of the other server

    "Source" means server from which we read stdout - remote server with RPC agent.
    "Target" means server for which we write data to stdin - another remote server with RPC agent.

    '*_command' arguments are a format strings
    '*_args' arguments are dictionaries with data to be substituted into format string,
      in the same way that is done when using "sh" and "sh_unchecked" methods of runner.

    :type source_runner: parallels.core.runners.windows.agent.WindowsAgentRunner
    :type source_command: str | unicode
    :type source_args: dict | None
    :type target_runner: parallels.core.runners.windows.agent.WindowsAgentRunner
    :type target_command: str | unicode
    :type target_args: dict | None
    :rtype: parallels.core.runners.utils.PipeCommandsResult
    """
    log_target_command_str = windows_utils.format_command_dict(
        target_command, clean_dict_args_for_log(target_args)
    )
    log_source_command_str = windows_utils.format_command_dict(
        source_command, clean_dict_args_for_log(source_args)
    )
    logger.fdebug(
        messages.PIPE_COMMAND_REMOTE_TO_REMOTE_START,
        source_server=source_runner.settings.ip,
        target_server=target_runner.settings.ip,
        command_source=log_source_command_str, command_target=log_target_command_str
    )

    source_stderr_data = stringio(is_unicode=False)
    target_stderr_data = stringio(is_unicode=False)
    result = PipeCommandsResult()

    def consume_target_stdout(data):
        # we are not interested in stdout on the target server - simply ignore it
        unused(data)

    def consume_target_stderr(data):
        target_stderr_data.write(data)

    def finalize_target(return_code):
        result.target_exit_code = return_code

    def error_target(error_message):
        result.target_error = error_message

    # Start process on the target server
    target_process_handle = target_runner.piped_sh_unchecked(
        target_command, target_args, consume_target_stdout, consume_target_stderr,
        finalize_target, error_target,
        # we are going to provide stdin to the process
        read_stdin=True
    )

    def consume_source_stdout(data):
        if result.target_error is None and result.target_exit_code is None:
            # write stdout from source server to stdin of target server
            target_process_handle.write_stdin(data)

    def consume_source_stderr(data):
        source_stderr_data.write(data)

    def finalize_source(return_code):
        result.source_exit_code = return_code
        # the process has finished on source server and there will be no more data on its stdout,
        # so close stdin on target server
        target_process_handle.close_stdin()

    def error_source(error_message):
        result.source_error = error_message
        # there were error receiving data, so we won't receive any data anymore,
        # so close stdin on target server
        target_process_handle.close_stdin()

    # Start process on the source server
    source_runner.piped_sh_unchecked(
        source_command, source_args,
        consume_source_stdout, consume_source_stderr,
        finalize_source, error_source
    )

    # Prepare result object and log results
    result.source_stderr = source_stderr_data.getvalue()
    source_stderr_data.close()
    result.target_stderr = target_stderr_data.getvalue()
    target_stderr_data.close()

    try:
        logger.fdebug(
            messages.PIPE_COMMAND_REMOTE_TO_REMOTE_FINISH,
            source_server=source_runner.settings.ip,
            target_server=target_runner.settings.ip,
            exit_code_target=result.target_exit_code,
            exit_code_source=result.source_exit_code,
            stderr_target=result.target_stderr,
            stderr_source=result.source_stderr,
            error_source=result.source_error,
            error_target=result.target_error
        )
    except MemoryError:
        logger.fdebug(messages.FAILED_TO_LOG_PIPE_COMMAND_RESULTS)
        logger.exception()
        logger.fdebug(
            messages.PIPE_COMMAND_REMOTE_TO_REMOTE_FINISH_TRUNCATED,
            source_server=source_runner.settings.ip,
            target_server=target_runner.settings.ip,
            exit_code_target=result.target_exit_code,
            exit_code_source=result.source_exit_code,
            stderr_target=result.target_stderr[-TRUNCATED_STDERR_LENGTH:],
            stderr_source=result.source_stderr[-TRUNCATED_STDERR_LENGTH:],
            error_source=result.source_error,
            error_target=result.target_error
        )

    return result


def pipe_command(runner, command, arguments, execution_options):
    """Run command on remote site and return stdout, stderr, exit code

    'command' argument is a format string
    'arguments' argument is a dictionary with data to be substituted into format string,
      in the same way that is done when using "sh" and "sh_unchecked" methods of runner.

    Implemented only for 1 case: piping from Web RPC agent runner.

    :type runner: parallels.core.runners.base.BaseRunner
    :type command: str | unicode
    :type arguments: dict | None
    :type execution_options: parallels.core.runners.entities.ExecutionOptions
    :rtype: parallels.core.runners.utils.PipeCommandResult
    """
    stdout_data = stringio(is_unicode=False)
    stderr_data = stringio(is_unicode=False)
    result = PipeCommandResult()

    def consume_stdout_function(data):
        stdout_data.write(data)

    def consume_stderr_function(data):
        stderr_data.write(data)

    def finalize_function(exit_code):
        result.exit_code = exit_code

    def error_function(error_message):
        raise MigrationError(error_message)

    runner.piped_sh_unchecked(
        command, arguments, execution_options,
        consume_stdout_function, consume_stderr_function,
        finalize_function, error_function
    )

    result.stdout = stdout_data.getvalue()
    stdout_data.close()
    result.stderr = stderr_data.getvalue()
    stderr_data.close()
    try:
        logger.fdebug(
            messages.PIPE_COMMAND_TO_FILE_FINISH,
            exit_code=result.exit_code,
            stdout=result.stdout,
            stderr=result.stderr,
        )
    except MemoryError:
        logger.fdebug(messages.FAILED_TO_LOG_PIPE_COMMAND_RESULTS)
        logger.exception()
        logger.fdebug(
            messages.PIPE_COMMAND_TO_FILE_FINISH_TRUNCATED,
            exit_code=result.exit_code,
            stdout_data=result.stdout[-TRUNCATED_STDERR_LENGTH:],
            stderr=result.stderr[-TRUNCATED_STDERR_LENGTH:],
        )

    return result


def pipe_command_to_file(runner, command, arguments, execution_options, file_path):
    """Run command on remote site and put stdout to file on local server

    'command' argument is a format string
    'arguments' argument is a dictionary with data to be substituted into format string,
      in the same way that is done when using "sh" and "sh_unchecked" methods of runner.

    Implemented only for 1 case: piping from Web RPC agent runner.

    :type runner: parallels.core.runners.base.BaseRunner
    :type command: str | unicode
    :type arguments: dict | None
    :type execution_options: parallels.core.runners.entities.ExecutionOptions
    :type file_path: str | unicode
    :rtype: parallels.core.runners.utils.PipeToFileCommandResult
    """
    fp = open_no_inherit(file_path, 'wb')
    stderr_data = stringio(is_unicode=False)
    result = PipeToFileCommandResult()

    def consume_stdout_function(data):
        fp.write(data)

    def consume_stderr_function(data):
        stderr_data.write(data)

    def finalize_function(exit_code):
        result.exit_code = exit_code

    def error_function(error_message):
        raise MigrationError(error_message)

    try:
        runner.piped_sh_unchecked(
            command, arguments, execution_options,
            consume_stdout_function, consume_stderr_function,
            finalize_function, error_function
        )
    finally:
        fp.close()

    result.stderr = stderr_data.getvalue()
    stderr_data.close()
    try:
        logger.fdebug(
            messages.PIPE_COMMAND_TO_FILE_FINISH,
            exit_code=result.exit_code,
            stderr=result.stderr,
        )
    except MemoryError:
        logger.fdebug(messages.FAILED_TO_LOG_PIPE_COMMAND_RESULTS)
        logger.exception()
        logger.fdebug(
            messages.PIPE_COMMAND_TO_FILE_FINISH_TRUNCATED,
            exit_code=result.exit_code,
            stderr=result.stderr[-TRUNCATED_STDERR_LENGTH:],
        )

    return result


class PipeToFileCommandResult(Entity):
    def __init__(self, exit_code=None, stderr=None):
        self._exit_code = exit_code
        self._stderr = stderr

    @property
    def exit_code(self):
        """Exit code of the command on the source server

        :rtype: int | None
        """
        return self._exit_code

    @exit_code.setter
    def exit_code(self, value):
        """Set exit code on the source server

        :type value: int
        :rtype: None
        """
        self._exit_code = value

    @property
    def stderr(self):
        """stderr as string on the source server

        :rtype: str | unicode | None
        """
        return self._stderr

    @stderr.setter
    def stderr(self, value):
        """Set stderr on the source server

        :type value: str | unicode
        :rtype: None
        """
        self._stderr = value


class PipeCommandResult(Entity):
    def __init__(
        self, exit_code=None, stderr=None, stdout=None,
    ):
        self._exit_code = exit_code
        self._stderr = stderr
        self._stdout = stdout

    @property
    def exit_code(self):
        """Exit code of the command

        :rtype: int | None
        """
        return self._exit_code

    @exit_code.setter
    def exit_code(self, value):
        """Set exit code

        :type value: int
        :rtype: None
        """
        self._exit_code = value

    @property
    def stdout(self):
        """stdout as string

        :rtype: str | unicode | None
        """
        return self._stdout

    @stdout.setter
    def stdout(self, value):
        """Set stdout

        :type value: str | unicode
        :rtype: None
        """
        self._stdout = value

    @property
    def stderr(self):
        """stderr as string

        :rtype: str | unicode | None
        """
        return self._stderr

    @stderr.setter
    def stderr(self, value):
        """Set stderr

        :type value: str | unicode
        :rtype: None
        """
        self._stderr = value


class PipeCommandsResult(Entity):
    def __init__(
        self, source_exit_code=None, source_stderr=None, source_error=None,
        target_exit_code=None, target_stderr=None, target_error=None
    ):
        self._source_exit_code = source_exit_code
        self._source_stderr = source_stderr
        self._source_error = source_error
        self._target_exit_code = target_exit_code
        self._target_stderr = target_stderr
        self._target_error = target_error

    @property
    def source_exit_code(self):
        """Exit code of the command on the source server

        :rtype: int | None
        """
        return self._source_exit_code

    @source_exit_code.setter
    def source_exit_code(self, value):
        """Set exit code on the source server

        :type value: int
        :rtype: None
        """
        self._source_exit_code = value

    @property
    def source_stderr(self):
        """stderr as string on the source server

        :rtype: str | unicode | None
        """
        return self._source_stderr

    @source_stderr.setter
    def source_stderr(self, value):
        """Set stderr on the source server

        :type value: str | unicode
        :rtype: None
        """
        self._source_stderr = value

    @property
    def source_error(self):
        """Internal/communication error as string on the source server

        :rtype: str | unicode | None
        """
        return self._source_error

    @source_error.setter
    def source_error(self, value):
        """Set internal/communication error as string on the source server

        :type value: str | unicode | None
        :rtype: None
        """
        self._source_error = value

    @property
    def target_exit_code(self):
        """Exit code of the command on the target server

        :rtype: int | None
        """
        return self._target_exit_code

    @target_exit_code.setter
    def target_exit_code(self, value):
        """Set exit code on the target server

        :type value: int
        :rtype: None
        """
        self._target_exit_code = value

    @property
    def target_stderr(self):
        """stderr as string on the target server

        :rtype: str | unicode | None
        """
        return self._target_stderr

    @target_stderr.setter
    def target_stderr(self, value):
        """Set stderr on the target server

        :type value: str | unicode
        :rtype: None
        """
        self._target_stderr = value

    @property
    def target_error(self):
        """Internal/communication error as string on the target server

        :rtype: str | unicode | None
        """
        return self._target_error

    @target_error.setter
    def target_error(self, value):
        """Set internal/communication error as string on the target server

        :type value: str | unicode | None
        :rtype: None
        """
        self._target_error = value
