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.

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)