Beispiel #1
0
    def connect(self):
        """Connect to the target device using the intermediate jumphosts."""
        device = None
        # logger.debug("Connecting to: {}".format(str(self)))
        for device in self.devices:
            if not device.connected:
                self.connection.emit_message("Connecting {}".format(str(device)), log_level=logging.INFO)
                protocol_name = device.get_protocol_name()
                device.protocol = make_protocol(protocol_name, device)
                self.ctrl.spawn_session(device.protocol.get_command())
                if device.connect(self.ctrl):
                    # logger.info("Connected to {}".format(device))
                    self.connection.emit_message("Connected {}".format(device), log_level=logging.INFO)
                else:
                    if device.last_error_msg:
                        message = device.last_error_msg
                        device.last_error_msg = None
                    else:
                        message = "Connection error"

                    logger.error(message)
                    raise ConnectionError(message)  # , host=str(device))

        if device is None:
            raise ConnectionError("No devices")

        return True
Beispiel #2
0
    def connect(self, driver):
        """Connect using the SSH protocol specific FSM."""
        #                      0                    1                 2
        events = [driver.password_re, self.device.prompt_re, driver.unable_to_connect_re,
                  #   3          4              5               6                   7
                  NEWSSHKEY, KNOWN_HOSTS, HOST_KEY_FAILED, MODULUS_TOO_SMALL, PROTOCOL_DIFFER,
                  #      8              9
                  driver.timeout_re, pexpect.TIMEOUT]

        transitions = [
            (driver.password_re, [0, 1, 4, 5], -1, partial(a_save_last_pattern, self), 0),
            (self.device.prompt_re, [0], -1, partial(a_save_last_pattern, self), 0),
            #  cover all messages indicating that connection was not set up
            (driver.unable_to_connect_re, [0], -1, a_unable_to_connect, 0),
            (NEWSSHKEY, [0], 1, partial(a_send_line, "yes"), 10),
            (KNOWN_HOSTS, [0, 1], 0, None, 0),
            (HOST_KEY_FAILED, [0], -1, ConnectionError("Host key failed", self.hostname), 0),
            (MODULUS_TOO_SMALL, [0], 0, self.fallback_to_sshv1, 0),
            (PROTOCOL_DIFFER, [0], 4, self.fallback_to_sshv1, 0),
            (PROTOCOL_DIFFER, [4], -1, ConnectionError("Protocol version differs", self.hostname), 0),
            (pexpect.TIMEOUT, [0], 5, partial(a_send, "\r\n"), 10),
            (pexpect.TIMEOUT, [5], -1, ConnectionTimeoutError("Connection timeout", self.hostname), 0),
            (driver.timeout_re, [0], -1, ConnectionTimeoutError("Connection timeout", self.hostname), 0),
        ]

        self.log("EXPECTED_PROMPT={}".format(pattern_to_str(self.device.prompt_re)))
        fsm = FSM("SSH-CONNECT", self.device, events, transitions, timeout=_C['connect_timeout'],
                  searchwindowsize=160)
        return fsm.run()
Beispiel #3
0
    def execute_command(self, cmd, timeout, wait_for_string, password):
        """Execute command."""
        try:
            self.last_command_result = None
            self.ctrl.send_command(cmd, password=password)
            if wait_for_string is None:
                wait_for_string = self.prompt_re

            # hide cmd in case it's password for further error messages or exceptions.
            if password:
                cmd = "*** Password ***"

            if not self.driver.wait_for_string(wait_for_string, timeout):
                self.chain.connection.log(
                    "Unexpected session disconnect during '{}' command execution"
                    .format(cmd))
                raise ConnectionError("Unexpected session disconnect",
                                      host=self.hostname)

            if self.last_command_result:
                output = self.last_command_result.replace('\r', '')
            else:
                output = self.ctrl.before.replace('\r', '')

            # not needed. Fixes the issue #11
            # second_line_index = output.find('\n') + 1
            # output = output[second_line_index:]
            return output

        except CommandSyntaxError as e:  # pylint: disable=invalid-name
            self.chain.connection.log("{}: '{}'".format(e.message, cmd))
            e.command = cmd
            # TODO: Verify why lint raises an issue
            raise e  # pylint: disable=raising-bad-type

        except (CommandTimeoutError, pexpect.TIMEOUT):
            self.chain.connection.log("Command timeout: '{}'".format(cmd))
            raise CommandTimeoutError(message="Command timeout",
                                      host=self.hostname,
                                      command=cmd)

        except ConnectionError as e:  # pylint: disable=invalid-name
            self.chain.connection.log("{}: '{}'".format(e.message, cmd))
            raise

        except pexpect.EOF:
            self.chain.connection.log("Unexpected session disconnect")
            raise ConnectionError("Unexpected session disconnect",
                                  host=self.hostname)

        except Exception as e:  # pylint: disable=invalid-name
            self.chain.connection.log("Exception {}".format(e))
            raise ConnectionError(message="Unexpected error",
                                  host=self.hostname)
Beispiel #4
0
    def connect(self):
        """Connect to the target device using the intermediate jumphosts."""
        device = None
        # logger.debug("Connecting to: {}".format(str(self)))
        for device in self.devices:
            if not device.connected:
                self.connection.emit_message("Connecting {}".format(
                    str(device)),
                                             log_level=logging.INFO)
                protocol_name = device.get_protocol_name()
                device.protocol = make_protocol(protocol_name, device)
                command = device.protocol.get_command()
                self.ctrl.spawn_session(command=command)
                try:
                    result = device.connect(self.ctrl)
                except CommandSyntaxError as exc:
                    # all commands during discovery provides itself in exception except
                    # spawn session which is handled differently. If command syntax error is detected during spawning
                    # a new session then the problem is either on jumphost or csm server.
                    # The problem with spawning session is detected in connect FSM for telnet and ssh.
                    if exc.command:
                        cmd = exc.command
                        host = device.hostname
                    else:
                        cmd = command
                        host = "Jumphost/CSM"

                    self.connection.log(
                        "Command not supported or not authorized on {}: '{}'".
                        format(host, cmd))
                    raise CommandError(
                        message="Command not supported or not authorized",
                        command=cmd,
                        host=host)

                if result:
                    # logger.info("Connected to {}".format(device))
                    self.connection.emit_message("Connected {}".format(device),
                                                 log_level=logging.INFO)
                else:
                    if device.last_error_msg:
                        message = device.last_error_msg
                        device.last_error_msg = None
                    else:
                        message = "Connection error"

                    self.connection.log(message)
                    raise ConnectionError(message)  # , host=str(device))

        if device is None:
            raise ConnectionError("No devices")

        return True
Beispiel #5
0
    def execute_command(self, cmd, timeout, wait_for_string, password):
        """Execute command."""
        try:
            self.last_command_result = None
            self.ctrl.send_command(cmd, password=password)
            if wait_for_string is None:
                wait_for_string = self.prompt_re

            if not self.driver.wait_for_string(wait_for_string, timeout):
                logger.error("Unexpected session disconnect during '{}' "
                             "command execution".format(cmd))
                raise ConnectionError("Unexpected session disconnect",
                                      host=self.hostname)

            if self.last_command_result:
                output = self.last_command_result.replace('\r', '')
            else:
                output = self.ctrl.before.replace('\r', '')

            # not needed. Fixes the issue #11
            # second_line_index = output.find('\n') + 1
            # output = output[second_line_index:]
            return output

        except CommandSyntaxError as e:  # pylint: disable=invalid-name
            logger.error("{}: '{}'".format(e.message, cmd))
            e.command = cmd
            raise e

        except (CommandTimeoutError, pexpect.TIMEOUT):
            logger.error("Command timeout: '{}'".format(cmd))
            raise CommandTimeoutError(message="Command timeout",
                                      host=self.hostname,
                                      command=cmd)

        except ConnectionError as e:  # pylint: disable=invalid-name
            logger.error("{}: '{}'".format(e.message, cmd))
            raise

        except pexpect.EOF:
            logger.error("Unexpected session disconnect")
            raise ConnectionError("Unexpected session disconnect",
                                  host=self.hostname)

        except Exception as e:  # pylint: disable=invalid-name
            logger.critical("Exception", exc_info=True)
            raise ConnectionError(message="Unexpected error",
                                  host=self.hostname)
Beispiel #6
0
def a_unexpected_prompt(ctx):
    """Provide message when received humphost prompt."""
    prompt = ctx.ctrl.after
    ctx.msg = "Received the jump host prompt: '{}'".format(prompt)
    ctx.device.connected = False
    ctx.finished = True
    raise ConnectionError("Unable to connect to the device.", ctx.ctrl.hostname)
Beispiel #7
0
    def wait_for_string(self, expected_string, timeout=60):
        """Wait for string FSM."""
        #                    0                         1                        2                        3
        events = [self.syntax_error_re, self.connection_closed_re, expected_string, self.press_return_re,
                  #        4           5                 6                7
                  self.more_re, pexpect.TIMEOUT, pexpect.EOF, self.buffer_overflow_re]

        # add detected prompts chain
        events += self.device.get_previous_prompts()  # without target prompt

        logger.debug("Expecting: {}".format(pattern_to_str(expected_string)))

        transitions = [
            (self.syntax_error_re, [0], -1, CommandSyntaxError("Command unknown", self.device.hostname), 0),
            (self.connection_closed_re, [0], 1, a_connection_closed, 10),
            (pexpect.TIMEOUT, [0], -1, CommandTimeoutError("Timeout waiting for prompt", self.device.hostname), 0),
            (pexpect.EOF, [0, 1], -1, ConnectionError("Unexpected device disconnect", self.device.hostname), 0),
            (self.more_re, [0], 0, partial(a_send, " "), 10),
            (expected_string, [0, 1], -1, a_expected_prompt, 0),
            (self.press_return_re, [0], -1, a_stays_connected, 0),
            # TODO: Customize in XR driver
            (self.buffer_overflow_re, [0], -1, CommandSyntaxError("Command too long", self.device.hostname), 0)
        ]

        for prompt in self.device.get_previous_prompts():
            transitions.append((prompt, [0, 1], 0, a_unexpected_prompt, 0))

        fsm = FSM("WAIT-4-STRING", self.device, events, transitions, timeout=timeout)
        return fsm.run()
Beispiel #8
0
    def enable(self, enable_password):
        """Change to the privilege mode."""
        if self.device.prompt[-1] == '#':
            self.log("Device is already in privileged mode")
            return

        events = [
            self.password_re, self.device.prompt_re, pexpect.TIMEOUT,
            pexpect.EOF
        ]
        transitions = [
            (self.password_re, [0], 1, partial(a_send_password,
                                               enable_password), 10),
            (self.password_re, [1], -1,
             ConnectionAuthenticationError("Incorrect enable password",
                                           self.device.hostname), 0),
            (self.device.prompt_re, [0, 1, 2, 3], -1, a_expected_prompt, 0),
            (pexpect.TIMEOUT, [0, 1, 2], -1,
             ConnectionAuthenticationError("Unable to get privileged mode",
                                           self.device.hostname), 0),
            (pexpect.EOF, [0, 1,
                           2], -1, ConnectionError("Device disconnected"), 0)
        ]
        self.device.ctrl.send_command(self.enable_cmd)
        fsm = FSM("IOS-ENABLE",
                  self.device,
                  events,
                  transitions,
                  timeout=10,
                  max_transitions=5)
        fsm.run()
        if self.device.prompt[-1] != '#':
            raise ConnectionAuthenticationError("Privileged mode not set",
                                                self.device.hostname)
Beispiel #9
0
    def authenticate(self, driver):
        """Authenticate using the SSH protocol specific FSM."""
        #              0                     1                    2                  3
        events = [
            driver.press_return_re, driver.password_re, self.device.prompt_re,
            pexpect.TIMEOUT
        ]

        transitions = [
            (driver.press_return_re, [0, 1], 1, partial(a_send, "\r\n"), 10),
            (driver.password_re, [0], 1,
             partial(a_send_password,
                     self._acquire_password()), _C['first_prompt_timeout']),
            (driver.password_re, [1], -1, a_authentication_error, 0),
            (self.device.prompt_re, [0, 1], -1, None, 0),
            (pexpect.TIMEOUT, [1], -1,
             ConnectionError("Error getting device prompt")
             if self.device.is_target else partial(a_send, "\r\n"), 0)
        ]

        self.log("EXPECTED_PROMPT={}".format(
            pattern_to_str(self.device.prompt_re)))
        fsm = FSM("SSH-AUTH",
                  self.device,
                  events,
                  transitions,
                  init_pattern=self.last_pattern,
                  timeout=30)
        return fsm.run()
Beispiel #10
0
    def spawn_session(self, command):
        """Spawn the session using proper command."""
        if self._session and self.isalive():  # pylint: disable=no-member
            self._connection.log("Executing command: '{}'".format(command))
            try:
                self.send(command)  # pylint: disable=no-member
                self.expect_exact(command, timeout=20)  # pylint: disable=no-member
                self.sendline()  # pylint: disable=no-member

            except (pexpect.EOF, OSError):
                raise ConnectionError("Connection error", self.hostname)
            except pexpect.TIMEOUT:
                raise ConnectionTimeoutError("Timeout", self.hostname)

        else:
            self._connection.log("Spawning command: '{}'".format(command))
            env = os.environ
            env['TERM'] = 'vt220'  # to avoid color control characters and make sure other env not intact, esp. PATH
            try:
                self._session = pexpect.spawn(
                    command,
                    maxread=65536,
                    searchwindowsize=4000,
                    env=env,
                    echo=True  # KEEP YOUR DIRTY HANDS OFF FROM ECHO!
                )
                self._session.delaybeforesend = 0.3
                self._connection.log("Child process FD: {}".format(
                    self._session.child_fd))
                rows, cols = self._session.getwinsize()
                if cols < 180:
                    self._session.setwinsize(512, 240)
                    nrows, ncols = self._session.getwinsize()
                    self._connection.log(
                        "Terminal window size changed from {}x{} to {}x{}".
                        format(rows, cols, nrows, ncols))
                else:
                    self._connection.log("Terminal window size: {}x{}".format(
                        rows, cols))

            except pexpect.EOF:
                raise ConnectionError("Connection error", self.hostname)
            except pexpect.TIMEOUT:
                raise ConnectionTimeoutError("Timeout", self.hostname)

            self.set_session_log(self._logfile_fd)
            self.connected = True
Beispiel #11
0
    def connect(self, driver):
        """Connect using console specific FSM."""
        #              0            1                    2                      3
        events = [
            ESCAPE_CHAR,
            driver.press_return_re,
            driver.standby_re,
            driver.username_re,
            #            4                   5            6                     7
            driver.password_re,
            driver.more_re,
            self.device.prompt_re,
            driver.rommon_re,
            #       8                           9              10             11
            driver.unable_to_connect_re,
            driver.timeout_re,
            pexpect.TIMEOUT,
            PASSWORD_OK
        ]

        transitions = [
            (ESCAPE_CHAR, [0], 1, partial(a_send,
                                          "\r\n"), _C['esc_char_timeout']),
            (driver.press_return_re, [0, 1], 1, partial(a_send, "\r\n"), 10),
            (PASSWORD_OK, [0, 1], 1, partial(a_send, "\r\n"), 10),
            (driver.standby_re, [0, 5], -1,
             ConnectionError("Standby console", self.hostname), 0),
            (driver.username_re, [0, 1, 5,
                                  6], -1, partial(a_save_last_pattern,
                                                  self), 0),
            (driver.password_re, [0, 1,
                                  5], -1, partial(a_save_last_pattern,
                                                  self), 0),
            (driver.more_re, [0, 5], 7, partial(a_send, "q"), 10),
            # router sends it again to delete
            (driver.more_re, [7], 8, None, 10),
            # (prompt, [0, 1, 5], 6, partial(a_send, "\r\n"), 10),
            (self.device.prompt_re, [0, 5], 0, None, 10),
            (self.device.prompt_re, [1, 6, 8,
                                     5], -1, partial(a_save_last_pattern,
                                                     self), 0),
            (driver.rommon_re, [0, 1, 5], -1, partial(a_save_last_pattern,
                                                      self), 0),
            (driver.unable_to_connect_re, [0, 1], -1, a_unable_to_connect, 0),
            (driver.timeout_re, [0, 1], -1,
             ConnectionTimeoutError("Connection Timeout", self.hostname), 0),
            (pexpect.TIMEOUT, [0, 1], 5, partial(a_send, "\r\n"), 10),
            (pexpect.TIMEOUT, [5], -1,
             ConnectionTimeoutError("Connection timeout", self.hostname), 0)
        ]
        logger.debug("EXPECTED_PROMPT={}".format(
            pattern_to_str(self.device.prompt_re)))
        fsm = FSM("CONSOLE-SERVER-CONNECT",
                  self.device,
                  events,
                  transitions,
                  timeout=_C['connect_timeout'],
                  init_pattern=self.last_pattern)
        return fsm.run()
Beispiel #12
0
    def connect(self, logfile=None, force_discovery=False):
        """Connect to the device.

        Args:
            logfile (file): Optional file descriptor for session logging. The file must be open for write.
                The session is logged only if ``log_session=True`` was passed to the constructor.
                If *None* then the default *session.log* file is created in `log_dir`.

            force_discovery (Bool): Optional. If True the device discover process will start after getting connected.

        Raises:
            ConnectionError: If the discovery method was not called first or there was a problem with getting
                the connection.

            ConnectionAuthenticationError: If the authentication failed.

            ConnectionTimeoutError: If the connection timeout happened.

        """
        logger.debug("-" * 20)
        logger.debug("Connecting")

        if logfile:
            self.session_fd = logfile

        self._clear_cache() if force_discovery else self._read_cache()

        excpt = ConnectionError("Could not connect to the device.")

        chains = len(self.connection_chains)
        for index, chain in enumerate(self.connection_chains, start=1):
            self.emit_message("Connection chain {}/{}: {}".format(index, chains, str(chain)), log_level=logging.INFO)

        begin = time.time()
        attempt = 1
        for index in self._chain_indices():
            self.emit_message("Connection chain/attempt [{}/{}]".format(index + 1, attempt), log_level=logging.INFO)

            chain = self.connection_chains[index]
            self._last_chain_index = index
            try:
                if chain.connect():
                    break
            except (ConnectionTimeoutError, ConnectionError) as e:  # pylint: disable=invalid-name
                self.emit_message("Connection error: {}".format(e), log_level=logging.ERROR)
                excpt = e

            attempt += 1
        else:
            # invalidate cache
            raise excpt

        self._write_cache()
        elapsed = time.time() - begin
        self.emit_message("Target device connected in {:.2f}s.".format(elapsed), log_level=logging.INFO)
        logger.debug("Connected")
        logger.debug("-" * 20)
Beispiel #13
0
    def spawn_session(self, command):
        """Spawn the session using proper command."""
        if self._session and self.isalive():  # pylint: disable=no-member
            logger.debug("Executing command: '{}'".format(command))
            try:
                self.send(command)  # pylint: disable=no-member
                self.expect_exact(command, timeout=20)  # pylint: disable=no-member
                self.sendline()  # pylint: disable=no-member

            except (pexpect.EOF, OSError):
                raise ConnectionError("Connection error", self.hostname)
            except pexpect.TIMEOUT:
                raise ConnectionTimeoutError("Timeout", self.hostname)

        else:
            logger.debug("Spawning command: '{}'".format(command))
            try:
                self._session = pexpect.spawn(
                    command,
                    maxread=65536,
                    searchwindowsize=4000,
                    env={"TERM": "VT100"},  # to avoid color control characters
                    echo=True  # KEEP YOUR DIRTY HANDS OFF FROM ECHO!
                )
                self._session.delaybeforesend = 0.3
                rows, cols = self._session.getwinsize()
                if cols < 160:
                    self._session.setwinsize(1024, 160)
                    nrows, ncols = self._session.getwinsize()
                    logger.debug("Terminal window size changed from "
                                 "{}x{} to {}x{}".format(
                                     rows, cols, nrows, ncols))
                else:
                    logger.debug("Terminal window size: {}x{}".format(
                        rows, cols))

            except pexpect.EOF:
                raise ConnectionError("Connection error", self.hostname)
            except pexpect.TIMEOUT:
                raise ConnectionTimeoutError("Timeout", self.hostname)

            self._session.logfile_read = self._logfile_fd
            self.connected = True
Beispiel #14
0
 def send_command(self, cmd, password=False):
     """Send command."""
     try:
         if password:
             self.waitnoecho(5)  # pylint: disable=no-member
             self.sendline(cmd)  # pylint: disable=no-member
         else:
             self.send(cmd)  # pylint: disable=no-member
             self.expect_exact([cmd, pexpect.TIMEOUT], timeout=15)  # pylint: disable=no-member
             self.sendline()  # pylint: disable=no-member
     except OSError:
         logger.error("Session already disconnected.")
         raise ConnectionError("Session already disconnected")
Beispiel #15
0
    def send(self, cmd="", timeout=60, wait_for_string=None, password=False):
        """Send the command to the device and return the output.

        Args:
            cmd (str): Command string for execution. Defaults to empty string.
            timeout (int): Timeout in seconds. Defaults to 60s
            wait_for_string (str): This is optional string that driver
                waits for after command execution. If none the detected
                prompt will be used.
            password (bool): If true cmd representing password is not logged
                and condoor waits for noecho.

        Returns:
            A string containing the command output.

        Raises:
            ConnectionError: General connection error during command execution
            CommandSyntaxError: Command syntax error or unknown command.
            CommandTimeoutError: Timeout during command execution

        """
        if self.connected:
            output = ''
            if password:
                self.chain.connection.log("Sending password")
            else:
                self.chain.connection.log("Sending command: '{}'".format(cmd))

            try:
                output = self.execute_command(cmd, timeout, wait_for_string,
                                              password)
            except ConnectionError:
                self.chain.connection.log("Connection lost. Disconnecting.")
                # self.disconnect()
                raise

            if password:
                self.chain.connection.log("Password sent successfully")
            else:
                self.chain.connection.log(
                    "Command executed successfully: '{}'".format(cmd))

            return output

        else:
            raise ConnectionError("Device not connected", host=self.hostname)
Beispiel #16
0
    def description_record(self):
        """Return dict describing :class:`condoor.Connection` object.

        Example::

            {'connections': [{'chain': [{'driver_name': 'eXR',
                             'family': 'ASR9K',
                             'hostname': 'vkg3',
                             'is_console': True,
                             'is_target': True,
                             'mode': 'global',
                             'os_type': 'eXR',
                             'os_version': '6.1.2.06I',
                             'platform': 'ASR-9904',
                             'prompt': 'RP/0/RSP0/CPU0:vkg3#',
                             'udi': {'description': 'ASR-9904 AC Chassis',
                                     'name': 'Rack 0',
                                     'pid': 'ASR-9904-AC',
                                     'sn': 'FOX2024GKDE ',
                                     'vid': 'V01'}}]},
                 {'chain': [{'driver_name': 'generic',
                             'family': None,
                             'hostname': '172.27.41.52:2045',
                             'is_console': None,
                             'is_target': True,
                             'mode': None,
                             'os_type': None,
                             'os_version': None,
                             'platform': None,
                             'prompt': None,
                             'udi': None}]}],
            'last_chain': 0}

        """
        if self.connection_chains:
            return {
                'connections': [{
                    'chain': [device.device_info for device in chain.devices]
                } for chain in self.connection_chains],
                'last_chain':
                self._last_chain_index,
            }
        else:
            raise ConnectionError("Device not connected")
Beispiel #17
0
 def send_command(self, cmd, password=False):
     """Send command."""
     try:
         if password:
             timeout = 10
             self._connection.log("Waiting for ECHO OFF")
             if self.waitnoecho(timeout):  # pylint: disable=no-member
                 self._connection.log("Password ECHO OFF received")
             else:
                 self._connection.log(
                     "Password ECHO OFF not received within {}s".format(
                         timeout))
             self.sendline(cmd)  # pylint: disable=no-member
         else:
             self.send(cmd)  # pylint: disable=no-member
             self.expect_exact([cmd, pexpect.TIMEOUT], timeout=15)  # pylint: disable=no-member
             self.sendline()  # pylint: disable=no-member
     except OSError:
         self._connection.log("Session already disconnected.")
         raise ConnectionError("Session already disconnected")
Beispiel #18
0
    def try_read_prompt(self, timeout_multiplier):
        """Read the prompt.

        Based on try_read_prompt from pxssh.py
        https://github.com/pexpect/pexpect/blob/master/pexpect/pxssh.py
        """
        # maximum time allowed to read the first response
        first_char_timeout = timeout_multiplier * 2

        # maximum time allowed between subsequent characters
        inter_char_timeout = timeout_multiplier * 0.4

        # maximum time for reading the entire prompt
        total_timeout = timeout_multiplier * 4

        prompt = ""
        begin = time()
        expired = 0.0
        timeout = first_char_timeout

        while expired < total_timeout:
            try:
                char = self.read_nonblocking(size=1, timeout=timeout)  # pylint: disable=no-member
                # \r=0x0d CR \n=0x0a LF
                if char not in ['\n',
                                '\r']:  # omit the cr/lf sent to get the prompt
                    timeout = inter_char_timeout
                expired = time() - begin
                prompt += char
            except pexpect.TIMEOUT:
                break
            except pexpect.EOF:
                raise ConnectionError('Session disconnected')

        prompt = prompt.strip()
        return prompt
Beispiel #19
0
    def run(self):
        """Start the FSM.

        Returns:
            boolean: True if FSM reaches the last state or false if the exception or error message was raised

        """
        ctx = FSM.Context(self.name, self.device)
        transition_counter = 0
        timeout = self.timeout
        self.log("{} Start".format(self.name))
        while transition_counter < self.max_transitions:
            transition_counter += 1
            try:
                start_time = time()
                if self.init_pattern is None:
                    ctx.event = self.ctrl.expect(
                        self.events,
                        searchwindowsize=self.searchwindowsize,
                        timeout=timeout)
                else:
                    self.log("INIT_PATTERN={}".format(
                        pattern_to_str(self.init_pattern)))
                    try:
                        ctx.event = self.events.index(self.init_pattern)
                    except ValueError:
                        self.log("INIT_PATTERN unknown.")
                        continue
                    finally:
                        self.init_pattern = None

                finish_time = time() - start_time
                key = (ctx.event, ctx.state)
                ctx.pattern = self.events[ctx.event]

                if key in self.transition_table:
                    transition = self.transition_table[key]
                    next_state, action_instance, next_timeout = transition
                    self.log("E={},S={},T={},RT={:.2f}".format(
                        ctx.event, ctx.state, timeout, finish_time))
                    if callable(
                            action_instance) and not isclass(action_instance):
                        if not action_instance(ctx):
                            self.log("Error: {}".format(ctx.msg))
                            return False
                    elif isinstance(action_instance, Exception):
                        self.log("A=Exception {}".format(action_instance))
                        raise action_instance
                    elif action_instance is None:
                        self.log("A=None")
                    else:
                        self.log("FSM Action is not callable: {}".format(
                            str(action_instance)))
                        raise RuntimeWarning("FSM Action is not callable")

                    if next_timeout != 0:  # no change if set to 0
                        timeout = next_timeout
                    ctx.state = next_state
                    self.log("NS={},NT={}".format(next_state, timeout))

                else:
                    self.log("Unknown transition: EVENT={},STATE={}".format(
                        ctx.event, ctx.state))
                    continue

            except EOF:
                raise ConnectionError("Session closed unexpectedly",
                                      self.ctrl.hostname)

            if ctx.finished or next_state == -1:
                self.log("{} Stop at E={},S={}".format(self.name, ctx.event,
                                                       ctx.state))
                return True

        # check while else if even exists
        self.log("FSM looped. Exiting")
        return False
Beispiel #20
0
 def update_driver(self, prompt):
     """Return driver name based on prompt analysis."""
     if "-stby" in prompt:
         raise ConnectionError("Standby console detected")
     return pattern_manager.platform(prompt, ['XE'])
Beispiel #21
0
    def reconnect(self, logfile=None, max_timeout=360, force_discovery=False):
        """Reconnect to the device.

        It can be called when after device reloads or the session was
        disconnected either by device or jumphost. If multiple jumphosts are used then `reconnect` starts from
        the last valid connection.

        Args:
            logfile (file): Optional file descriptor for session logging. The file must be open for write.
                The session is logged only if ``log_session=True`` was passed to the constructor.
                It the parameter is not passed then the default *session.log* file is created in `log_dir`.

            max_timeout (int): This is the maximum amount of time during the session tries to reconnect. It may take
                longer depending on the TELNET or SSH default timeout.

            force_discovery (Bool): Optional. If True the device discover process will start after getting connected.

        Raises:
            ConnectionError: If the discovery method was not called first or there was a problem with getting
             the connection.

            ConnectionAuthenticationError: If the authentication failed.

            ConnectionTimeoutError: If the connection timeout happened.

        """
        if logfile:
            self.session_fd = logfile

        if force_discovery:
            self._clear_cache()
            # self.disconnect()
        else:
            self._read_cache()

        chain_indices = self._chain_indices()

        excpt = ConnectionError("Could not (re)connect to the device")

        chains = len(self.connection_chains)
        for index, chain in enumerate(self.connection_chains, start=1):
            self.emit_message("Connection chain {}/{}: {}".format(index, chains, str(chain)), log_level=logging.INFO)

        self.emit_message("Trying to (re)connect within {} seconds".format(max_timeout), log_level=logging.INFO)
        sleep_time = 0
        begin = time.time()
        attempt = 1
        elapsed = 0

        while max_timeout - elapsed > 0:
            if sleep_time > 0:
                self.emit_message("Waiting {:.0f}s before next connection attempt".format(sleep_time), log_level=logging.INFO)
                time.sleep(sleep_time)

            # up
            elapsed = time.time() - begin
            # logger.debug("Connection attempt {} Elapsed {:.1f}s".format(attempt, elapsed))
            try:
                index = chain_indices[0]
                self.emit_message("Connection chain/attempt [{}/{}]".format(index + 1, attempt),
                                  log_level=logging.INFO)

                chain = self.connection_chains[index]
                self._last_chain_index = index
                if chain.connect():
                    break
            except (ConnectionTimeoutError, ConnectionError) as e:  # pylint: disable=invalid-name
                if chain.ctrl.is_connected:
                    prompt = chain.ctrl.detect_prompt()
                    index = chain.get_device_index_based_on_prompt(prompt)
                    chain.tail_disconnect(index)

                self.emit_message("Connection error: {}".format(e), log_level=logging.ERROR)
                chain_indices.rotate(-1)
                excpt = e
            finally:
                # TODO: Make a configuration parameter
                elapsed = time.time() - begin
                sleep_time = min(30, max_timeout - elapsed)
                self.emit_message("Time elapsed {:.0f}s/{:.0f}s".format(elapsed, max_timeout), log_level=logging.INFO)

            attempt += 1
        else:
            self.emit_message("Unable to (re)connect within {:.0f}s".format(elapsed), log_level=logging.ERROR)
            raise excpt

        self._write_cache()
        self.emit_message("Target device connected in {:.0f}s.".format(elapsed), log_level=logging.INFO)
        logger.debug("-" * 20)
Beispiel #22
0
    def wait_for_string(self, expected_string, timeout=60):
        """Wait for string FSM for XR 64 bit."""
        ADMIN_USERNAME_PROMPT = re.compile("Admin Username:"******"Password:"******"Expecting: {}".format(pattern_to_str(expected_string)))
        self.log("Calvados prompt: {}".format(pattern_to_str(
            self.calvados_re)))

        transitions = [
            (ADMIN_USERNAME_PROMPT, [0], 6,
             partial(a_send_username, self.device.node_info.username), 5),
            (ADMIN_PASSWORD_PROMPT, [0, 6], 0,
             partial(a_send_password, self.device.node_info.password), 5),
            (self.authentication_error_re, [0], -1,
             ConnectionAuthenticationError("Admin plane authentication failed",
                                           self.device.hostname), 0),
            (self.syntax_error_re, [0], -1,
             CommandSyntaxError("Command unknown", self.device.hostname), 0),
            (self.connection_closed_re, [0], 1, a_connection_closed, 10),
            (pexpect.TIMEOUT, [0, 2], -1,
             CommandTimeoutError("Timeout waiting for prompt",
                                 self.device.hostname), 0),
            (pexpect.EOF, [0, 1], -1,
             ConnectionError("Unexpected device disconnect",
                             self.device.hostname), 0),
            (self.more_re, [0], 0, partial(a_send, " "), 10),
            (expected_string, [0, 1], -1, a_expected_prompt, 0),
            (self.calvados_re, [0], -1, a_expected_prompt, 0),
            (self.press_return_re, [0], -1, a_stays_connected, 0),
            (self.calvados_connect_re, [0], 2, None, 0),
            # admin command to switch to calvados
            (self.calvados_re, [2], 3, None, _C['calvados_term_wait_time']),
            # getting the prompt only
            (pexpect.TIMEOUT, [3], 0, partial(a_send, "\r\r"), timeout),
            # term len
            (self.calvados_term_length, [3], 4, None, 0),
            # ignore for command start
            (self.calvados_re, [4], 5, None, 0),
            # ignore for command start
            (self.calvados_re, [5], 0, a_store_cmd_result, 0),
        ]

        for prompt in self.device.get_previous_prompts():
            transitions.append((prompt, [0, 1], 0, a_unexpected_prompt, 0))

        fsm = FSM("WAIT-4-STRING",
                  self.device,
                  events,
                  transitions,
                  timeout=timeout)
        return fsm.run()
Beispiel #23
0
    def reload(self, reload_timeout, save_config):
        """Reload the device."""
        MAX_BOOT_TIME = 1800  # 30 minutes - TODO(klstanie): move to config
        RELOAD_PROMPT = re.compile(
            re.escape("Reload hardware module ? [no,yes]"))

        START_TO_BACKUP = re.compile("Status report.*START TO BACKUP")
        BACKUP_IN_PROGRESS = re.compile("Status report.*BACKUP INPROGRESS")

        BACKUP_HAS_COMPLETED_SUCCESSFULLY = re.compile(
            "Status report.*BACKUP HAS COMPLETED SUCCESSFULLY")
        DONE = re.compile(re.escape("[Done]"))

        STAND_BY = re.compile("Please stand by while rebooting the system")
        CONSOLE = re.compile(
            "ios con[0|1]/(?:RS?P)?[0-1]/CPU0 is now available")
        CONFIGURATION_COMPLETED = re.compile("SYSTEM CONFIGURATION COMPLETED")
        CONFIGURATION_IN_PROCESS = re.compile(
            "SYSTEM CONFIGURATION IN PROCESS")
        BOOTING = re.compile(
            "Booting IOS-XR 64 bit Boot previously installed image")

        #           0                 1              2                         3                          4     5
        events = [
            RELOAD_PROMPT,
            START_TO_BACKUP,
            BACKUP_IN_PROGRESS,
            BACKUP_HAS_COMPLETED_SUCCESSFULLY,
            DONE,
            BOOTING,
            #   6                  7               8                     9                         10
            CONSOLE,
            self.press_return_re,
            CONFIGURATION_COMPLETED,
            CONFIGURATION_IN_PROCESS,
            self.username_re,
            # 11          12           13           14
            EOF,
            pexpect.TIMEOUT,
            self.rommon_re,
            STAND_BY
        ]

        transitions = [
            # do I really need to clean the cmd
            (RELOAD_PROMPT, [0], 1, partial(a_send_line,
                                            "yes"), MAX_BOOT_TIME),
            (START_TO_BACKUP, [0, 1], 2, a_message_callback, 60),
            (BACKUP_IN_PROGRESS, [0, 1, 2], 2, a_message_callback, 60),
            (BACKUP_HAS_COMPLETED_SUCCESSFULLY, [0, 1, 2], 3,
             a_message_callback, 10),
            (DONE, [1, 2, 3], 4, None, MAX_BOOT_TIME),
            (STAND_BY, [2, 3, 4], 5, a_message_callback, MAX_BOOT_TIME),
            (self.rommon_re, [0, 4], 5, partial(a_send_boot,
                                                "boot"), MAX_BOOT_TIME),
            (BOOTING, [0, 1, 2, 3, 4], 5, a_message_callback, MAX_BOOT_TIME),
            (CONSOLE, [0, 1, 2, 3, 4, 5], 6, None, 600),
            (self.press_return_re, [6], 7, partial(a_send, "\r"), 300),
            (CONFIGURATION_IN_PROCESS, [7], 8, None, 180),
            (CONFIGURATION_COMPLETED, [8], -1, a_reconnect, 0),
            (self.username_re, [9], -1, a_return_and_reconnect, 0),
            (EOF, [0, 1, 2, 3, 4,
                   5], -1, ConnectionError("Device disconnected"), 0),
            (pexpect.TIMEOUT, [7], 9, partial(a_send, "\r"), 180),
            (pexpect.TIMEOUT, [1, 5, 8], -1,
             ConnectionError(
                 "Boot process took more than {}s".format(MAX_BOOT_TIME)), 0),
            (pexpect.TIMEOUT, [9], -1,
             ConnectionAuthenticationError(
                 "Unable to reconnect after reloading"), 0)
        ]

        fsm = FSM("RELOAD", self.device, events, transitions, timeout=600)
        return fsm.run()
Beispiel #24
0
    def reconnect(self,
                  logfile=None,
                  max_timeout=360,
                  force_discovery=False,
                  tracefile=None,
                  retry=True):
        """Reconnect to the device.

        It can be called when after device reloads or the session was
        disconnected either by device or jumphost. If multiple jumphosts are used then `reconnect` starts from
        the last valid connection.

        Args:
            logfile (file): Optional file descriptor for session logging. The file must be open for write.
                The session is logged only if ``log_session=True`` was passed to the constructor.
                It the parameter is None the default *session.log* file is created in `log_dir`.

            max_timeout (int): This is the maximum amount of time during the session tries to reconnect. It may take
                longer depending on the TELNET or SSH default timeout.

            force_discovery (Bool): Optional. If True the device discover process will start after getting connected.

            tracefile (file): Optional file descriptor for condoor logging. The file must be open for write.
                It the parameter is None the default *condoor.log* file is created in `log_dir`.

            retry (bool): Optional parameter causing the connnection to retry until timeout


        Raises:
            ConnectionError: If the discovery method was not called first or there was a problem with getting
             the connection.

            ConnectionAuthenticationError: If the authentication failed.

            ConnectionTimeoutError: If the connection timeout happened.

        """
        self._enable_logging(logfile, tracefile)

        self.log("-" * 20)
        self.log("Condoor Version {}".format(__version__))
        self.log("Cache filename: {}".format(_CACHE_FILE))

        self.connection_chains = [
            Chain(self, url_list) for url_list in normalize_urls(self._urls)
        ]
        self.log("Connecting")

        self._clear_cache() if force_discovery else self._read_cache()

        excpt = ConnectionError("Could not (re)connect to the device")
        chains = len(self.connection_chains)

        chain_indices = self._chain_indices()
        for index, chain in enumerate(self.connection_chains, start=1):
            self.emit_message("Connection chain {}/{}: {}".format(
                index, chains, str(chain)),
                              log_level=logging.INFO)

        self.emit_message(
            "Trying to (re)connect within {} seconds".format(max_timeout),
            log_level=logging.INFO)
        sleep_time = 0
        begin = time.time()
        attempt = 1
        elapsed = 0

        while max_timeout - elapsed > 0:
            # there is no reason to wait for another chain.
            if (attempt - 1) % len(self.connection_chains) != 0:
                sleep_time = 2

            if sleep_time > 0:
                self.emit_message(
                    "Waiting {:.0f}s before next connection attempt".format(
                        sleep_time),
                    log_level=logging.INFO)
                time.sleep(sleep_time)

            elapsed = time.time() - begin
            try:
                index = chain_indices[0]
                self.emit_message("Connection chain/attempt [{}/{}]".format(
                    index + 1, attempt),
                                  log_level=logging.INFO)

                chain = self.connection_chains[index]
                self._last_chain_index = index
                if chain.connect():
                    break
            except (ConnectionTimeoutError, ConnectionError) as e:  # pylint: disable=invalid-name
                chain.disconnect()
                self.emit_message("Connection error: {}".format(e),
                                  log_level=logging.INFO)
                chain_indices.rotate(-1)
                excpt = e

            except Exception as e:
                self.log("Exception hit: {}".format(str(e)))
                chain.disconnect()
                self.emit_message("Connection error: {}".format(e),
                                  log_level=logging.INFO)
                chain_indices.rotate(-1)
                excpt = e

            finally:
                # TODO: Make a configuration parameter
                elapsed = time.time() - begin
                sleep_time = min(30, max_timeout - elapsed)
                self.emit_message("Time elapsed {:.0f}s/{:.0f}s".format(
                    elapsed, max_timeout),
                                  log_level=logging.INFO)

            attempt += 1
            if attempt > len(self.connection_chains) and not retry:
                self.emit_message(
                    "Unable to (re)connect within {:.0f}s".format(elapsed),
                    log_level=logging.ERROR)
                self._clear_cache()
                self._disable_logging()
                raise excpt
        else:
            self.emit_message(
                "Unable to (re)connect within {:.0f}s".format(elapsed),
                log_level=logging.ERROR)
            self._clear_cache()
            self._disable_logging()
            raise excpt

        self._write_cache()
        self.emit_message(
            "Target device connected in {:.0f}s.".format(elapsed),
            log_level=logging.INFO)
        self.log("-" * 20)
Beispiel #25
0
    def wait_for_string(self, expected_string, timeout=60):
        """Wait for string FSM for XR 64 bit."""
        # Big thanks to calvados developers for make this FSM such complex ;-)
        #                    0                         1                        2                        3
        events = [
            self.syntax_error_re,
            self.connection_closed_re,
            expected_string,
            self.press_return_re,
            #        4           5                 6                7               8
            self.more_re,
            pexpect.TIMEOUT,
            pexpect.EOF,
            self.calvados_re,
            self.calvados_connect_re,
            #     9
            self.calvados_term_length
        ]

        # add detected prompts chain
        events += self.device.get_previous_prompts()  # without target prompt

        logger.debug("Expecting: {}".format(pattern_to_str(expected_string)))
        logger.debug("Calvados prompt: {}".format(
            pattern_to_str(self.calvados_re)))

        transitions = [
            (self.syntax_error_re, [0], -1,
             CommandSyntaxError("Command unknown", self.device.hostname), 0),
            (self.connection_closed_re, [0], 1, a_connection_closed, 10),
            (pexpect.TIMEOUT, [0, 2], -1,
             CommandTimeoutError("Timeout waiting for prompt",
                                 self.device.hostname), 0),
            (pexpect.EOF, [0, 1], -1,
             ConnectionError("Unexpected device disconnect",
                             self.device.hostname), 0),
            (self.more_re, [0], 0, partial(a_send, " "), 10),
            (expected_string, [0, 1], -1, a_expected_prompt, 0),
            (self.calvados_re, [0], -1, a_expected_prompt, 0),
            (self.press_return_re, [0], -1, a_stays_connected, 0),
            (self.calvados_connect_re, [0], 2, None, 0),
            # admin command to switch to calvados
            (self.calvados_re, [2], 3, None, _C['calvados_term_wait_time']),
            # getting the prompt only
            (pexpect.TIMEOUT, [3], 0, partial(a_send, "\r"), 0),
            # term len
            (self.calvados_term_length, [3], 4, None, 0),
            # ignore for command start
            (self.calvados_re, [4], 5, None, 0),
            # ignore for command start
            (self.calvados_re, [5], 0, a_store_cmd_result, 0),
        ]

        for prompt in self.device.get_previous_prompts():
            transitions.append((prompt, [0, 1], 0, a_unexpected_prompt, 0))

        fsm = FSM("WAIT-4-STRING",
                  self.device,
                  events,
                  transitions,
                  timeout=timeout)
        return fsm.run()
Beispiel #26
0
    def reload(self, reload_timeout, save_config):
        """Reload the device."""
        RELOAD_PROMPT = re.compile(
            re.escape("Reload hardware module ? [no,yes]"))
        START_TO_BACKUP = re.compile("Status report.*START TO BACKUP")
        BACKUP_HAS_COMPLETED_SUCCESSFULLY = re.compile(
            "Status report.*BACKUP HAS COMPLETED SUCCESSFULLY")
        DONE = re.compile(re.escape("[Done]"))
        CONSOLE = re.compile(
            "ios con[0|1]/(?:RS?P)?[0-1]/CPU0 is now available")
        CONFIGURATION_COMPLETED = re.compile("SYSTEM CONFIGURATION COMPLETED")
        CONFIGURATION_IN_PROCESS = re.compile(
            "SYSTEM CONFIGURATION IN PROCESS")
        BOOTING = re.compile(
            "Booting IOS-XR 64 bit Boot previously installed image")

        # events = [RELOAD_NA, DONE, PROCEED, CONFIGURATION_IN_PROCESS, self.rommon_re, self.press_return_re,
        #           #   6               7                       8                     9      10        11
        #           CONSOLE, CONFIGURATION_COMPLETED, RECONFIGURE_USERNAME_PROMPT, TIMEOUT, EOF, self.reload_cmd,
        #           #    12                    13                     14
        #           ROOT_USERNAME_PROMPT, ROOT_PASSWORD_PROMPT, CANDIDATE_BOOT_IMAGE]

        events = [
            self.reload_cmd, RELOAD_PROMPT, START_TO_BACKUP,
            BACKUP_HAS_COMPLETED_SUCCESSFULLY, DONE, BOOTING, CONSOLE,
            self.press_return_re, CONFIGURATION_COMPLETED,
            CONFIGURATION_IN_PROCESS, EOF
        ]

        transitions = [
            # do I really need to clean the cmd
            (RELOAD_PROMPT, [0], 1, partial(a_send_line, "yes"), 30),
            (START_TO_BACKUP, [1], 2, a_message_callback, 60),
            (BACKUP_HAS_COMPLETED_SUCCESSFULLY, [2], 3, a_message_callback,
             10),
            (DONE, [3], 4, None, 600),
            (self.rommon_re, [0, 4], 5, partial(a_send_boot, "boot"), 600),
            (BOOTING, [0, 4], 5, a_message_callback, 600),
            (CONSOLE, [0, 5], 6, None, 600),
            (self.press_return_re, [6], 7, partial(a_send, "\r"), 300),
            (CONFIGURATION_IN_PROCESS, [7], 8, None, 180),
            (CONFIGURATION_COMPLETED, [8], -1, a_reconnect, 0),
            (EOF, [0, 1, 2, 3, 4,
                   5], -1, ConnectionError("Device disconnected"), 0),

            # (RELOAD_NA, [1], -1, a_reload_na, 0),
            # (DONE, [1], 2, None, 120),
            # (PROCEED, [2], 3, partial(a_send, "\r"), reload_timeout),
            # (self.rommon_re, [0, 3], 4, partial(a_send_boot, "boot"), 600),
            # (CANDIDATE_BOOT_IMAGE, [0, 3], 4, a_message_callback, 600),
            # (CONSOLE, [0, 1, 3, 4], 5, None, 600),
            # (self.press_return_re, [5], 6, partial(a_send, "\r"), 300),
            # # configure root username and password the same as used for device connection.
            # (RECONFIGURE_USERNAME_PROMPT, [6, 7], 8, None, 10),
            # (ROOT_USERNAME_PROMPT, [8], 9, partial(a_send_username, self.device.node_info.username), 1),
            # (ROOT_PASSWORD_PROMPT, [9], 9, partial(a_send_password, self.device.node_info.password), 1),
            # (CONFIGURATION_IN_PROCESS, [6, 9], 7, None, 180),
            # (CONFIGURATION_COMPLETED, [7], -1, a_reconnect, 0),
            # (TIMEOUT, [0, 1, 2], -1, ConnectionAuthenticationError("Unable to reload"), 0),
            # (EOF, [0, 1, 2, 3, 4, 5], -1, ConnectionError("Device disconnected"), 0),
            # (TIMEOUT, [6], 7, partial(a_send, "\r"), 180),
            # (TIMEOUT, [7], -1, ConnectionAuthenticationError("Unable to reconnect after reloading"), 0),
        ]

        fsm = FSM("RELOAD", self.device, events, transitions, timeout=600)
        return fsm.run()
Beispiel #27
0
def a_standby_console(ctx):
    """Raise ConnectionError exception when connected to standby console."""
    ctx.device.is_console = True
    ctx.ctrl.disconnect()
    raise ConnectionError("Standby console", ctx.ctrl.hostname)
Beispiel #28
0
 def _chain(self):
     try:
         return self.connection_chains[self._last_chain_index]
     except TypeError:
         raise ConnectionError("Device not connected")