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
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()
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)
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
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)
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)
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()
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)
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()
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
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()
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)
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
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")
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)
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")
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")
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
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
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'])
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)
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()
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()
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)
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()
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()
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)
def _chain(self): try: return self.connection_chains[self._last_chain_index] except TypeError: raise ConnectionError("Device not connected")