def double_timeout_channel(connected_channel): connected_channel._check_console_mode = mock.Mock( side_effect=exceptions.CmdlineTimeout( timeout=libvirtchannel.DEFAULT_EXPECT_TIMEOUT)) connected_channel.expect = mock.Mock(side_effect=exceptions.CmdlineTimeout( timeout=libvirtchannel.DEFAULT_EXPECT_TIMEOUT)) return connected_channel
def _handle_init_login(self, match_res, timeout): """ Handle initial login. :param match_res: Pattern(s) of prompts to look for after login. 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 after login. :raises CmdlineTimeout: if any step of the login exceeds the timeout. """ # Add login prompt and password prompt so that we can detect # what require for login reg_with_login_prompts = match_res reg_with_login_prompts.insert(0, self.PASSWORD_PROMPT) reg_with_login_prompts.insert(0, self.LOGIN_PROMPT) (index, match, data) = self.channel.expect(reg_with_login_prompts, timeout) if index == 0: # username is required for login logging.debug("Sending login user ...") # Encode text to ascii; telnetlib does not work well with unicode # literals. text_to_send = (self._user + self.ENTER_LINE).encode('ascii') self.channel.write(text_to_send) (index, match, data) = self.channel.expect(reg_with_login_prompts, timeout) if index == 1: # password is required for login logging.debug("Sending password ...") # Encode text to ascii; telnetlib does not work well with unicode # literals. text_to_send = (self._password + self.ENTER_LINE).encode('ascii') self.channel.write(text_to_send) (index, match, data) = self.channel.expect(reg_with_login_prompts, timeout) # At this point, we should already logged in; raises exceptions if not if index < 0: raise exceptions.CmdlineTimeout(timeout=timeout, failed_match=match_res) elif index in (0, 1): logging.info("Login failed, still waiting for %s prompt", ('username' if index == 0 else 'password')) raise exceptions.CmdlineTimeout( timeout=timeout, failed_match=reg_with_login_prompts[index]) # Login successfully if reach this point logging.info('Telnet channel to "%s" started' % self._host) return match
def bad_password_channel(connected_channel): connected_channel._check_console_mode = mock.Mock(return_value=MATCH_LOGIN) connected_channel.expect = mock.Mock(side_effect=( ('', MATCH_PASSWORD), exceptions.CmdlineTimeout( timeout=libvirtchannel.DEFAULT_EXPECT_TIMEOUT), )) return connected_channel
def initial_timeout_channel(connected_channel): connected_channel._check_console_mode = mock.Mock( side_effect=exceptions.CmdlineTimeout( timeout=libvirtchannel.DEFAULT_EXPECT_TIMEOUT)) # We expect to restart the login prompt matching process after a timeout. connected_channel.expect = mock.Mock(side_effect=( ('', MATCH_LOGIN), ('', MATCH_PASSWORD), ('', MATCH_ROOT), )) return connected_channel
def expect(self, match_res, timeout=60): """ Waits for some 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. :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. """ match_res, safe_match_text = self._expect_init(match_res) (index, matched, data) = self.channel.expect(match_res, timeout) if index == -1: raise exceptions.CmdlineTimeout(timeout=timeout, failed_match=match_res) # Remove matched string at the end length = matched.start() - matched.end() if length < 0: data = data[:length] # Normalize carriage returns data = self.fixup_carriage_returns(data) return (data, matched)
def bad_username_channel(connected_channel): connected_channel._check_console_mode = mock.Mock(return_value=MATCH_LOGIN) connected_channel.expect = mock.Mock( side_effect=exceptions.CmdlineTimeout( timeout=libvirtchannel.DEFAULT_EXPECT_TIMEOUT)) return connected_channel
def expectsig(signum, frame): raise exceptions.CmdlineTimeout(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')
def _exec_paramiko_command(self, command, timeout, retry_count, retry_delay): try: channel = self.sshprocess.transport.open_session() except socket.error: if retry_count == 0: logging.error("Socket Error %s" % traceback.format_exc()) raise exceptions.ConnectionError # Reconnect and try again logging.info("connection seems broken, reconnect...") self._reconnect(retry_count=retry_count, retry_delay=retry_delay) channel = self.sshprocess.transport.open_session() # Put stderr into the same output as stdout. channel.set_combine_stderr(True) starttime = time.time() # Paramiko 1.7.5 has a bug in its internal event system # that can cause it to sometimes throw a 'not connected' exception when # running exec_command. If we get that exception here, but we're still # connected, then just eat the exception and go on, since that's the # normal case. This will hopefully be fixed in 1.7.6 and this # try/except removed. try: channel.exec_command(command) except paramiko.SSHException: if not self.sshprocess.is_connected(): logging.info("Not connected to %s", self._host) logging.error("SSHException %s" % traceback.format_exc()) raise exceptions.ConnectionError else: logging.debug('Ignore Paramiko SSHException due to 1.7.5 bug') chan_closed = False output = "" # Read until we time out or the channel closes while not chan_closed: # 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([channel], [], [], 10) # Timeout if this is taking too long. if timeout and ((time.time() - starttime) > timeout): raise exceptions.CmdlineTimeout(command=command, timeout=timeout) # If the reader-ready list isn't empty, then read. We know it must # be channel here, since thats all we're waiting on. if len(readers) > 0: data = channel.recv(4096) # If we get no data back, the channel has closed. if len(data) > 0: output += data else: chan_closed = True elif channel.exit_status_ready(): # If no readers were available, see if the # exit status is ready - if so, the channel must be closed. # exit_status_ready can return true before we've read all the # data. Problem is, I know I've seen it return true when there # was no data, and then data came in afterwards, so this might # occasionally trip early. If only paramiko.channel had a way # to see if it was closed.. chan_closed = True # Done reading. Now we need to wait for the exit status/channel close. # Rather than block here, we'll poll to ensure we don't get stuck. while not channel.exit_status_ready(): if timeout and ((time.time() - starttime) > timeout): raise exceptions.CmdlineTimeout(command=command, timeout=timeout) else: time.sleep(0.5) exit_status = channel.recv_exit_status() channel.close() return output, exit_status