import os.path import socket from pathlib import Path from urllib.parse import urlparse import frappe.utils from frappe import get_conf from frappe.exceptions import UrlSchemeNotSupported REDIS_KEYS = ("redis_cache", "redis_queue") def can_connect(sock: socket.socket, address: tuple[str, int] | str, timeout: int | float) -> bool: """ Check whether we can connect to a socket address. Args: sock: The socket object to use for the connection. address: The address to connect to (tuple for network, string for unix). timeout: Connection timeout in seconds. Returns: True if connection was successful, False otherwise. """ sock.settimeout(timeout) try: sock.connect(address) sock.shutdown(socket.SHUT_RDWR) return True except OSError: return False finally: sock.close() def is_open( scheme: str, hostname: str | None, port: int | str | None, path: str | None, timeout: int | float = 10, ) -> bool: """ Check if a service is reachable via socket connection. Args: scheme: The URL scheme (redis, mariadb, etc.) or 'unix'. hostname: The remote host to connect to. port: The port number to connect to. path: The path to the unix socket (if scheme is 'unix'). timeout: Connection timeout in seconds. Returns: True if the service is reachable, False otherwise. """ if scheme in ("redis", "rediss", "postgres", "mariadb"): if not hostname or not port: return False try: addresses = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) except (socket.gaierror, TypeError): return False # Try all addresses returned by getaddrinfo for family, socket_type, proto, _, address in addresses: s = socket.socket(family, socket_type, proto) if can_connect(s, address, timeout): return True return False if scheme == "unix": if not path: return False s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) return can_connect(s, path, timeout) raise UrlSchemeNotSupported(scheme) def check_database(): config = get_conf() db_type = config.get("db_type", "mariadb") if db_type == "sqlite": db_path = Path(frappe.utils.get_site_path()) / "db" / f"{config.db_name}.db" return {db_type: db_path.is_file() and os.access(db_path, os.R_OK | os.W_OK)} if db_socket := config.get("db_socket"): return {db_type: is_open("unix", None, None, db_socket)} db_host = config.get("db_host", "127.0.0.1") db_port = config.get("db_port", 3306 if db_type == "mariadb" else 5432) return {db_type: is_open(db_type, db_host, db_port, None)} def check_redis(redis_services=None): config = get_conf() services = redis_services or REDIS_KEYS status = {} for srv in services: url = urlparse(config[srv]) status[srv] = is_open(url.scheme, url.hostname, url.port, url.path) return status def check_connection(redis_services=None): service_status = {} service_status.update(check_database()) service_status.update(check_redis(redis_services)) return service_status