def _verify_connected(self):
        """
        Helper function that verifies the connection has been established
        and that the transport object we are using is still connected.

        :raises ConnectionError: if we are not connected
        """

        if not self.channel:
            raise exceptions.ConnectionError(
                context='Channel has not been started')

        if not self.sshprocess.is_connected():
            raise exceptions.ConnectionError(
                context='Host SSH shell has been disconnected')
    def open_interactive_channel(self, term='console', width=80, height=24):
        """
        Creates and starts a stateful interactive channel.

        This should be used whenever the channel must remain open between
        commands for interactive processing, or when a terminal/tty is
        necessary; e.g., CLIs with modes.

        :param term: terminal type to emulate; defaults to 'console'
        :param width: width (in characters) of the terminal screen;
            defaults to 80
        :param height: height (in characters) of the terminal screen;
            defaults to 24

        :return: A Paramiko channel that communicate with the remote end in
            a stateful way.

        :raises ConnectionError: if the SSH connection has not yet been
            established.
        """

        if (not self.is_connected()):
            raise exceptions.ConnectionError(context='Not connected!')

        channel = self.transport.open_session()
        channel.get_pty(term, width, height)
        channel.invoke_shell()
        channel.set_combine_stderr(True)
        return channel
    def __init__(self,
                 hostname,
                 username,
                 password=None,
                 private_key_path=None,
                 port=DEFAULT_PORT,
                 terminal='console',
                 width=DEFAULT_TERM_WIDTH,
                 height=DEFAULT_TERM_HEIGHT,
                 **kwargs):

        self.conn_port = port

        if password is None and private_key_path is None:
            cause = 'Either password or path to private key must be included.'
            raise exceptions.ConnectionError(cause=cause)

        pkey = None
        if private_key_path is not None:
            with open(private_key_path, 'r') as f:
                pkey = paramiko.rsakey.RSAKey.from_private_key(f)

        self.sshprocess = sshprocess.SSHProcess(host=hostname,
                                                user=username,
                                                password=password,
                                                private_key=pkey,
                                                port=self.conn_port)
        self._host = hostname
        self._term = terminal
        self._term_width = width
        self._term_height = height

        self.channel = None
    def _verify_domain_running(self):
        """
        Make sure domain is running.

        :raises ConnectionError: if it is not.
        """

        info = self._domain.info()
        state = info[0]
        if state != libvirt.VIR_DOMAIN_RUNNING:
            raise exceptions.ConnectionError(
                context="Domain %s is not in running state" %
                self._machine_name)
Example #5
0
    def _reconnect(self, retry_count, retry_delay):
        if not isinstance(retry_count, int) or retry_count < 1:
            raise TypeError("retry_count should be positive int")
        if not isinstance(retry_delay, int) or retry_delay < 1:
            raise TypeError("retry_delay should be positive int")

        for count in range(retry_count):
            try:
                self.sshprocess.connect()
                return
            except exceptions.ConnectionError:
                logging.info("sleep %d second and retry..." % retry_delay)
                time.sleep(retry_delay)

        raise exceptions.ConnectionError("Failed to connect after "
                                         "%d retries" % retry_count)
    def _verify_connected(self):
        """
        Verifies an established connection and transport.

        :raises ConnectionError: if we are not connected
        """

        if not self.channel:
            raise exceptions.ConnectionError(
                context='Channel has not been started')

        # Send an NOP to see whether connection is still alive.
        try:
            self.channel.sock.sendall(telnetlib.IAC + telnetlib.NOP)
        except socket.error:
            logging.exception('Host SSH shell has been disconnected')
            raise exceptions.ConnectionError
    def send(self, text_to_send):
        """
        Sends text to the channel immediately.  Does not wait for any response.

        :param text_to_send: Text to send, may be an empty string.
        """
        self._verify_connected()

        logging.debug('Sending "%s"' % self.safe_line_feeds(text_to_send))

        bytes_sent = 0

        while bytes_sent < len(text_to_send):
            bytes_sent_this_time = self.channel.send(text_to_send[bytes_sent:])
            if bytes_sent_this_time == 0:
                raise exceptions.ConnectionError(context='Channel is closed')
            bytes_sent += bytes_sent_this_time
    def start(self, match_res=(ROOT_PROMPT, ), timeout=DEFAULT_EXPECT_TIMEOUT):
        """
        Opens a console and logs in.

        :param match_res: Pattern(s) of prompts to look for.
            May be a single regex string, or a list of them.
        :param timeout: maximum time, in seconds, to wait for a regular
            expression match. 0 to wait forever.

        :return: Python :class:`re.MatchObject` containing data on
            what was matched.
        """

        if not HAS_LIBVIRT:
            raise ImportError("Failed to import libvirt")

        if not match_res:
            match_res = [self.ROOT_PROMPT]
        elif not (isinstance(match_res, list) or isinstance(match_res, tuple)):
            match_res = [
                match_res,
            ]

        try:
            # Get connection and libvirt domain
            self._conn = libvirt.open(self._uri)
            self._domain = self._conn.lookupByName(self._machine_name)
        except libvirt.libvirtError:
            raise exceptions.ConnectionError(
                context="Failed to find domain '%s' on host" %
                self._machine_name)

        # Make sure domain is running
        self._verify_domain_running()

        # open console
        self._stream = self._conn.newStream(0)
        console_flags = libvirt.VIR_DOMAIN_CONSOLE_FORCE
        self._domain.openConsole(None, self._stream, console_flags)

        return self._handle_init_login(match_res, timeout)
    def expect(self, match_res, timeout=DEFAULT_EXPECT_TIMEOUT):
        """
        Waits for text to be received that matches one or more regex patterns.

        Note that data may have been received before this call and is waiting
        in the buffer; you may want to call receive_all() to flush the receive
        buffer before calling send() and call this function to match the
        output from your send() only.

        :param match_res: Pattern(s) to look for to be considered successful.
                          May be a single regex string, or a list of them.
                          Currently cannot match multiple lines.
        :param timeout: maximum time, in seconds, to wait for a regular
                        expression match. 0 to wait forever.

        :return: ``(output, match_object)`` where output is the output of
            the command (without the matched text), and match_object is a
            Python :class:`re.MatchObject` containing data on what was matched.

            You may use ``MatchObject.string[m.start():m.end()]`` to recover
            the actual matched text, which will be unicode.

            ``re.MatchObject.pattern`` will contain the pattern that matched,
            which will be one of the elements of match_res passed in.

        :raises CmdlineTimeout: if no match found before timeout.
        :raises ConnectionError: if the channel is closed.
        """

        match_res, safe_match_text = self._expect_init(match_res)
        received_data = ''

        # Index into received_data marking the start of the first unprocessed
        # line.
        next_line_start = 0

        starttime = time.time()

        while True:
            # Use select to check whether channel is ready for read.
            # Reading on the channel directly would block until data is
            # ready, where select blocks at most 10 seconds which allows
            # us to check whether the specified timeout has been reached.
            # If channel is not ready for reading within 10 seconds,
            # select returns an empty list to 'readers'.
            (readers, w, x) = select.select([self.channel], [], [], 10)

            # Timeout if this is taking too long.
            if timeout and ((time.time() - starttime) > timeout):
                partial_output = repr(self.safe_line_feeds(received_data))
                raise exceptions.CmdlineTimeout(command=None,
                                                output=partial_output,
                                                timeout=timeout,
                                                failed_match=match_res)

            new_data = None

            # We did not find clear documentation in Paramiko on how to check
            # whether a channel is closed unexpectedly. Our current logic is
            # that a channel is closed if:
            #   (1) read the channel and get 0 bytes, or
            #   (2) channel is not ready for reading but exit_status_ready()
            # Our experiments has shown that this correctly handles detecting
            # if a channel has been unexpected closed.
            if len(readers) > 0:
                new_data = self.channel.recv(4096)

                if len(new_data) == 0:
                    # Channel closed
                    raise exceptions.ConnectionError(
                        failed_match=match_res,
                        context='Channel unexpectedly closed')

                # If we're still here, we have new data to process.
                received_data, new_lines = self._process_data(
                    new_data, received_data, next_line_start)

                output, match = self._match_lines(received_data,
                                                  next_line_start, new_lines,
                                                  match_res)

                if (output, match) != (None, None):
                    return output, match

                # Update next_line_start to be the index of the last \n
                next_line_start = received_data.rfind('\n') + 1

            elif self.channel.exit_status_ready():
                raise exceptions.ConnectionError(
                    failed_match=match_res,
                    context='Channel unexpectedly closed')