def test_send_response(self):
     conn = terminal_connection.RawConnection()
     # no responses
     self.assertEqual(conn._send_response("abcd?", []), -1)
     # wrong question
     self.assertEqual(
         conn._send_response("abcd?", [{
             'question': 'yes?',
             'answer': 'no'
         }]), -1)
     # correct question
     conn.conn = MagicMock()
     conn.logger = MagicMock()
     conn.conn.send = Mock(return_value=2)
     conn.conn.closed = False
     conn.conn.log_file_name = False
     self.assertEqual(
         conn._send_response("continue, yes?", [{
             'question': 'yes?',
             'answer': 'no'
         }]), 14)
     conn.conn.send.assert_called_with("no")
     # question with new line response
     conn.conn.send = Mock(return_value=1)
     self.assertEqual(
         conn._send_response("continue, yes?", [{
             'question': 'yes?',
             'answer': 'n',
             'newline': True
         }]), 14)
     conn.conn.send.assert_has_calls([call("n"), call('\n')])
    def test_run_with_closed_connection_after_third_check(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()

        class _fake_conn(object):

            call_count = 0

            def send(self, text):
                return len(text)

            def recv(self, size):
                return "+\n"

            def close(self):
                pass

            @property
            def closed(self):
                self.call_count += 1

                return (self.call_count >= 4)

        conn.conn = _fake_conn()

        self.assertEqual(conn.run("test"), "+")
    def test_write_to_log_no_logfile(self):
        conn = terminal_connection.RawConnection()
        conn.log_file_name = None
        conn.logger = MagicMock()

        conn._write_to_log("Some_text")
        conn.logger.debug.assert_not_called()
 def test_connect_raw(self):
     conn_mock = MagicMock()
     conn_mock.recv = MagicMock(return_value=b"some_prompt#")
     ssh_mock = MagicMock()
     ssh_mock.connect = MagicMock()
     ssh_mock.invoke_shell = MagicMock(return_value=conn_mock)
     conn = terminal_connection.RawConnection(logger=MagicMock(),
                                              log_file_name=None)
     # without responses
     with patch("paramiko.SSHClient", MagicMock(return_value=ssh_mock)):
         self.assertEqual(
             conn.connect("ip",
                          "user",
                          "password",
                          None,
                          port=44,
                          prompt_check=None), "some_prompt")
     conn_mock.send = MagicMock(return_value=7)
     conn_mock.recv = MagicMock(
         return_value=b"Confirm Password:\nsome_prompt#")
     # with responses
     with patch("paramiko.SSHClient", MagicMock(return_value=ssh_mock)):
         self.assertEqual(
             conn.connect("ip",
                          "user",
                          "password",
                          None,
                          port=44,
                          prompt_check=None,
                          responses=[{
                              "question": "Confirm Password:"******"answer": "123456"
                          }]), "some_prompt")
    def test_connect_with_key(self):
        ssh_mock = MagicMock()
        ssh_mock.connect = MagicMock(side_effect=OSError("e"))
        with patch("paramiko.RSAKey.from_private_key",
                   MagicMock(return_value="key_value")):
            with patch("paramiko.SSHClient", MagicMock(return_value=ssh_mock)):
                with self.assertRaises(OSError):
                    conn = terminal_connection.RawConnection(
                        logger="logger", log_file_name="log_file_name")
                    conn.connect(
                        "ip",
                        "user",
                        None,
                        "key",
                        prompt_check=None,
                    )

        ssh_mock.connect.assert_called_with('ip',
                                            allow_agent=False,
                                            pkey='key_value',
                                            port=22,
                                            timeout=5,
                                            username='******')

        self.assertEqual(conn.logger, "logger")
        self.assertEqual(conn.log_file_name, "log_file_name")
 def test_delete_backspace(self):
     conn = terminal_connection.RawConnection()
     # simple case
     self.assertEqual(conn._delete_backspace("abc\bd\n$a\bbc"), "abd\n$bc")
     # \b in begging of line
     self.assertEqual(conn._delete_backspace("\bcd\n$a\bbc"), "cd\n$bc")
     # \b at the end
     self.assertEqual(conn._delete_backspace("abc\b\b\b\b\b"), "")
    def test_cleanup_response_error(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()

        # check with closed connection
        with self.assertRaises(exceptions.RecoverableError) as error:
            conn._cleanup_response(text="prompt> text\n some\nerror",
                                   prefix="prompt>",
                                   warning_examples=[],
                                   error_examples=['error'],
                                   critical_examples=[])

        conn.logger.info.assert_not_called()

        self.assertEqual(
            repr(error.exception),
            'RecoverableError(\'Looks as we have error in response:  '
            'text\\n some\\nerror\',)')

        # check with alive connection
        conn.conn = MagicMock()
        conn.conn.closed = False

        # save mocks
        _conn_mock = conn.conn

        # warnings?
        with self.assertRaises(exceptions.RecoverableWarning) as error:
            conn._cleanup_response(text="prompt> text\n some\nerror",
                                   prefix="prompt>",
                                   warning_examples=['error'],
                                   error_examples=[],
                                   critical_examples=[])
        _conn_mock.close.assert_not_called()

        # errors?
        with self.assertRaises(exceptions.RecoverableError) as error:
            conn._cleanup_response(text="prompt> text\n some\nerror",
                                   prefix="prompt>",
                                   warning_examples=[],
                                   error_examples=['error'],
                                   critical_examples=[])
        _conn_mock.close.assert_called_with()
        self.assertFalse(conn.conn)
        conn.conn = _conn_mock

        # critical?
        conn.conn.close = MagicMock()
        # save mocks
        _conn_mock = conn.conn
        # check with close
        with self.assertRaises(exceptions.NonRecoverableError) as error:
            conn._cleanup_response(text="prompt> text\n some\nerror",
                                   prefix="prompt>",
                                   warning_examples=[],
                                   error_examples=[],
                                   critical_examples=['error'])
        _conn_mock.close.assert_called_with()
    def test_cleanup_response_empty(self):
        conn = terminal_connection.RawConnection()

        self.assertEqual(
            conn._cleanup_response(text=" text ",
                                   prefix=":",
                                   warning_examples=[],
                                   error_examples=[],
                                   critical_examples=[]), "text")
    def test_recv(self):
        conn = terminal_connection.RawConnection()
        conn.conn = MagicMock()
        conn.logger = MagicMock()
        conn.conn.recv = MagicMock(return_value="AbCd")
        conn.conn.log_file_name = False

        self.assertEqual(conn._conn_recv(4), "AbCd")

        conn.conn.recv.assert_called_with(4)
    def test_send_closed_connection(self):
        conn = terminal_connection.RawConnection()
        conn.conn = MagicMock()
        conn.conn.send = MagicMock(return_value=3)
        conn.conn.closed = True
        conn.conn.log_file_name = False

        conn._conn_send("abcd")

        conn.conn.send.assert_called_with("abcd")
    def test_run_with_closed_connection(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()
        conn.conn = MagicMock()
        conn.conn.closed = True
        conn.conn.send = MagicMock(return_value=5)

        self.assertEqual(conn.run("test"), "")

        conn.conn.send.assert_called_with("test\n")
    def test_send_byte_by_byte(self):
        conn = terminal_connection.RawConnection()
        conn.conn = MagicMock()
        conn.logger = MagicMock()
        conn.conn.send = Mock(return_value=2)
        conn.conn.closed = False
        conn.conn.log_file_name = False

        conn._conn_send("abcd")

        conn.conn.send.assert_has_calls([call('abcd'), call('cd')])
    def test_recv_empty(self):
        conn = terminal_connection.RawConnection()
        conn.conn = MagicMock()
        conn.logger = MagicMock()
        conn.conn.recv = MagicMock(return_value="")
        conn.conn.log_file_name = False

        self.assertEqual(conn._conn_recv(4), "")

        conn.logger.warn.assert_called_with('We have empty response.')
        conn.conn.recv.assert_called_with(4)
    def test_run_return_without_delay(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()
        conn.conn = MagicMock()
        conn.conn.closed = False
        conn.conn.send = MagicMock(return_value=5)
        conn.conn.recv = MagicMock(return_value="\nmessage\n#")

        self.assertEqual(conn.run("test"), "message")

        conn.conn.send.assert_called_with("test\n")
    def test_send_troubles(self):
        conn = terminal_connection.RawConnection()
        conn.conn = MagicMock()
        conn.logger = MagicMock()
        conn.conn.send = MagicMock(return_value=-1)
        conn.conn.closed = True
        conn.conn.log_file_name = False

        conn._conn_send("abcd")

        conn.logger.info.assert_called_with("We have issue with send!")
        conn.conn.send.assert_called_with("abcd")
    def test_is_closed(self):
        conn = terminal_connection.RawConnection()

        conn.conn = MagicMock()

        conn.conn.closed = False
        self.assertFalse(conn.is_closed())

        conn.conn.closed = True
        self.assertTrue(conn.is_closed())

        conn.conn = None
        self.assertTrue(conn.is_closed())
    def test_cleanup_response_without_prompt(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()

        self.assertEqual(
            conn._cleanup_response(text="prmpt> text ",
                                   prefix="prompt>",
                                   warning_examples=[],
                                   error_examples=['error'],
                                   critical_examples=[]), "prmpt> text")

        conn.logger.debug.assert_called_with(
            "Have not found 'prompt>' in response: ''prmpt> text ''")
    def test_cleanup_response_with_prompt(self):
        conn = terminal_connection.RawConnection()

        conn.logger = MagicMock()

        self.assertEqual(
            conn._cleanup_response(text="prompt> text ",
                                   prefix="prompt>",
                                   warning_examples=[],
                                   error_examples=['error'],
                                   critical_examples=[]), "text")

        conn.logger.info.assert_not_called()
 def test_connect(self):
     conn_mock = MagicMock()
     conn_mock.recv = MagicMock(return_value=b"some_prompt#")
     ssh_mock = MagicMock()
     ssh_mock.connect = MagicMock()
     ssh_mock.invoke_shell = MagicMock(return_value=conn_mock)
     conn = terminal_connection.RawConnection(
         logger=MagicMock(), log_file_name=None)
     with patch("paramiko.SSHClient", MagicMock(return_value=ssh_mock)):
         self.assertEqual(
             conn.connect("ip", "user", "password", None, port=44,
                          prompt_check=None),
             "some_prompt"
         )
    def test_write_to_log_cantcreate_dir(self):
        conn = terminal_connection.RawConnection()
        conn.log_file_name = '/proc/read_only/file'
        conn.logger = MagicMock()

        with patch("os.path.isdir", MagicMock(return_value=False)):
            with patch(
                    "os.makedirs",
                    MagicMock(side_effect=Exception(
                        "[Errno 13] Permission denied: '/proc/read_only'"))):
                conn._write_to_log("Some_text")
        conn.logger.info.assert_called_with(
            'Can\'t write to log: Exception("[Errno 13] Permission denied:'
            ' \'/proc/read_only\'",)')
    def test_cleanup_response_mess_before_prompt(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()

        self.assertEqual(
            conn._cleanup_response(text="..prompt> text\n some",
                                   prefix="prompt>",
                                   warning_examples=[],
                                   error_examples=['error'],
                                   critical_examples=[]), "some")

        conn.logger.debug.assert_called_with(
            "Some mess before 'prompt>' in response: ''..prompt> "
            "text\\n some''")
    def test_run_return_without_delay_with_responses(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()
        conn.conn = MagicMock()
        conn.conn.closed = False
        conn.conn.send = MagicMock(side_effect=[5, 2])
        conn.conn.recv = MagicMock(side_effect=["\nmessage, yes?", "ok\n#"])

        self.assertEqual(
            conn.run("test", responses=[{
                'question': 'yes?',
                'answer': 'no'
            }]), "message, yes?ok")

        conn.conn.send.assert_has_calls([call("test\n"), call('no')])
    def test_close(self):
        conn = terminal_connection.RawConnection()

        conn_conn = MagicMock()
        conn_conn.close = MagicMock()
        conn.conn = conn_conn

        conn_ssh = MagicMock()
        conn_ssh.close = MagicMock()
        conn.ssh = conn_ssh

        conn.close()

        conn_conn.close.assert_called_with()
        conn_ssh.close.assert_called_with()
    def test_connect_with_password(self):
        ssh_mock = MagicMock()
        ssh_mock.connect = MagicMock(side_effect=OSError("e"))
        with patch("paramiko.SSHClient", MagicMock(return_value=ssh_mock)):
            with self.assertRaises(OSError):
                conn = terminal_connection.RawConnection(
                    logger="logger", log_file_name="log_file_name")
                conn.connect("ip", "user", "password", None, port=44,
                             prompt_check="prompt_check")

        ssh_mock.connect.assert_called_with(
            'ip', allow_agent=False, look_for_keys=False, password='******',
            port=44, timeout=5, username='******')

        self.assertEqual(conn.logger, "logger")
        self.assertEqual(conn.log_file_name, "log_file_name")
    def test_write_to_log_write_file_output(self):
        conn = terminal_connection.RawConnection()
        conn.log_file_name = '/proc/read_only_file'
        conn.logger = MagicMock()

        with patch("os.path.isdir", MagicMock(return_value=True)):
            fake_file = mock_open()
            if PY2:
                # python 2
                with patch('__builtin__.open', fake_file):
                    conn._write_to_log("Some_text")
            else:
                # python 3
                with patch('builtins.open', fake_file):
                    conn._write_to_log("Some_text")
            fake_file.assert_called_once_with('/proc/read_only_file', 'a+')
            fake_file().write.assert_called_with('Some_text')
    def test_close(self):
        conn = terminal_connection.RawConnection()

        conn.conn = MagicMock()
        conn.conn.close = MagicMock()
        conn.ssh = MagicMock()
        conn.ssh.close = MagicMock()

        # save mocks
        _conn_mock = conn.conn
        _ssh_mock = conn.ssh

        # run commands
        conn.close()

        # check calls
        _conn_mock.close.assert_called_with()
        _ssh_mock.close.assert_called_with()
    def test_run_with_closed_connection_after_twice_check(self):
        conn = terminal_connection.RawConnection()
        conn.logger = MagicMock()
        conn.conn = MagicMock()
        conn.conn.closed = False

        conn.conn.call_count = 0

        def _recv(size):

            if conn.conn.call_count == 1:
                conn.conn.closed = True

            conn.conn.call_count += 1

            return "+"

        conn.conn.send = MagicMock(return_value=5)
        conn.conn.recv = _recv

        self.assertEqual(conn.run("test"), "++")

        conn.conn.send.assert_called_with("test\n")
    def test_find_any_in(self):
        conn = terminal_connection.RawConnection()

        self.assertEqual(conn._find_any_in("abcd\n$abc", ["$", "#"]), 5)
        self.assertEqual(conn._find_any_in("abcd\n>abc", ["$", "#"]), -1)
def _execute(ctx, properties, runtime_properties, get_resource, host_ip,
             log_stamp, kwargs):
    # register logger file
    logger_file = kwargs.get('logger_file')
    if logger_file:
        fh = logging.FileHandler(logger_file)
        fh.setLevel(logging.DEBUG)
        fh.setFormatter(
            logging.Formatter(fmt="%(asctime)s %(levelname)s %(message)s",
                              datefmt="%Y-%m-%d %H:%M:%S"))
        ctx.logger.addHandler(fh)

    # get current calls
    calls = kwargs.get('calls', [])
    if not calls:
        ctx.logger.info("No calls")
        return

    # credentials
    terminal_auth = properties.get('terminal_auth', {})
    terminal_auth.update(kwargs.get('terminal_auth', {}))
    ip_list = terminal_auth.get('ip')

    # if node contained in some other node, try to overwrite ip
    if not ip_list and host_ip:
        ip_list = [host_ip]
        ctx.logger.info("Used host from container: {ip_list}".format(
            ip_list=repr(ip_list)))

    if isinstance(ip_list, bytes):
        ip_list = ip_list.decode('UTF-8')
    if isinstance(ip_list, text_type):
        ip_list = [ip_list]
    user = terminal_auth.get('user')
    password = terminal_auth.get('password')
    key_content = terminal_auth.get('key_content')
    port = terminal_auth.get('port', 22)

    if not ip_list or not user:
        raise cfy_exc.NonRecoverableError(
            "please check your credentials, ip or user not set")

    # additional settings
    global_promt_check = terminal_auth.get('promt_check')
    global_warning_examples = terminal_auth.get('warnings', [])
    global_error_examples = terminal_auth.get('errors', [])
    global_critical_examples = terminal_auth.get('criticals', [])
    global_responses = terminal_auth.get('responses', [])
    exit_command = terminal_auth.get('exit_command', 'exit')
    smart_device = terminal_auth.get('smart_device')

    # save logs to debug file
    log_file_name = None
    if terminal_auth.get('store_logs'):
        log_file_name = "/tmp/terminal-{log_stamp}.log".format(
            log_stamp=log_stamp)
        ctx.logger.info("Communication logs will be saved to %s" %
                        log_file_name)

    if smart_device:
        ctx.logger.info("Used ssh shell extension.")
        connection = terminal_connection.SmartConnection(
            logger=ctx.logger, log_file_name=log_file_name)
    else:
        ctx.logger.info("Used raw stream connection.")
        connection = terminal_connection.RawConnection(
            logger=ctx.logger, log_file_name=log_file_name)

    for ip in ip_list:
        try:
            prompt = connection.connect(ip,
                                        user,
                                        password,
                                        key_content,
                                        port,
                                        prompt_check=global_promt_check,
                                        responses=global_responses)
            ctx.logger.info("Will be used: " + ip)
            break

        except Exception as ex:
            ctx.logger.info(
                "Can't connect to:{} with exception:{} and type:{}".format(
                    repr(ip), str(ex), str(type(ex))))
    else:
        raise cfy_exc.OperationRetry(message="Let's try one more time?")

    ctx.logger.info(
        "Device prompt: {prompt}".format(prompt=shorted_text(prompt)))

    for call in calls:
        responses = call.get('responses', global_responses)
        promt_check = call.get('promt_check', global_promt_check)
        error_examples = call.get('errors', global_error_examples)
        warning_examples = call.get('warnings', global_warning_examples)
        critical_examples = call.get('criticals', global_critical_examples)
        # use action if exist
        operation = call.get('action', "")
        # use template if have
        if not operation and 'template' in call:
            template_name = call.get('template')
            template_params = call.get('params')
            template = get_resource(template_name)
            if not template:
                ctx.logger.info("Empty template.")
                continue
            if not template_params:
                template_params = {}
            # save context for reuse in template
            template_params['ctx'] = ctx
            operation = render_template(template.decode('utf-8'),
                                        template_params)

        # incase of template_text
        if not operation and 'template_text' in call:
            template_params = call.get('params')
            template = call.get('template_text')
            if not template:
                ctx.logger.info("Empty template_text.")
                continue
            if not template_params:
                template_params = {}
            # save context for reuse in template
            template_params['ctx'] = ctx
            operation = render_template(template, template_params)

        if not operation:
            continue

        if responses:
            ctx.logger.info("We have predefined responses: {responses}".format(
                responses=shorted_text(responses)))

        ctx.logger.debug("Template: \n{operation}".format(
            operation=shorted_text(obfuscate_passwords(operation))))

        result = ""
        for op_line in operation.split("\n"):
            # skip empty lines
            if not op_line.strip():
                continue

            ctx.logger.info("Executing template...")
            ctx.logger.debug("Execute: {opline}".format(
                opline=shorted_text(obfuscate_passwords(op_line))))

            result_part = rerun(ctx=ctx,
                                func=connection.run,
                                args=[],
                                kwargs={
                                    "command": op_line,
                                    "prompt_check": promt_check,
                                    "error_examples": error_examples,
                                    "warning_examples": warning_examples,
                                    "critical_examples": critical_examples,
                                    "responses": responses
                                },
                                retry_count=call.get('retry_count', 10),
                                retry_sleep=call.get('retry_sleep', 15))

            if result_part.strip():
                ctx.logger.info(shorted_text(result_part))

            result += (result_part + "\n")
        # save results to runtime properties
        save_to = call.get('save_to')
        if save_to:
            ctx.logger.info(
                "For save: {result}".format(result=shorted_text(result)))
            runtime_properties[save_to] = result.strip()

    while not connection.is_closed() and exit_command:
        ctx.logger.info("Execute close")
        result = rerun(ctx=ctx,
                       func=connection.run,
                       args=[],
                       kwargs={
                           "command": exit_command,
                           "prompt_check": promt_check,
                           "warning_examples": global_warning_examples,
                           "error_examples": global_error_examples,
                           "critical_examples": global_error_examples
                       })
        ctx.logger.info(
            "Result of close: {result}".format(result=shorted_text(result)))
        time.sleep(1)

    connection.close()
 def test_empty_send(self):
     conn = terminal_connection.RawConnection()
     conn._conn_send("")