| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  | import inspect | 
					
						
							| 
									
										
										
										
											2025-03-18 15:25:31 +08:00
										 |  |  | from urllib.parse import urlparse | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-16 08:16:11 +08:00
										 |  |  | import logging | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  | import redis | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-16 08:11:08 +08:00
										 |  |  | from open_webui.env import REDIS_SENTINEL_MAX_RETRY_COUNT | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-16 08:16:11 +08:00
										 |  |  | log = logging.getLogger(__name__) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 10:22:29 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  | class SentinelRedisProxy: | 
					
						
							|  |  |  |     def __init__(self, sentinel, service, *, async_mode: bool = True, **kw): | 
					
						
							|  |  |  |         self._sentinel = sentinel | 
					
						
							|  |  |  |         self._service = service | 
					
						
							|  |  |  |         self._kw = kw | 
					
						
							|  |  |  |         self._async_mode = async_mode | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _master(self): | 
					
						
							|  |  |  |         return self._sentinel.master_for(self._service, **self._kw) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __getattr__(self, item): | 
					
						
							|  |  |  |         master = self._master() | 
					
						
							|  |  |  |         orig_attr = getattr(master, item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if not callable(orig_attr): | 
					
						
							|  |  |  |             return orig_attr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         FACTORY_METHODS = {"pipeline", "pubsub", "monitor", "client", "transaction"} | 
					
						
							|  |  |  |         if item in FACTORY_METHODS: | 
					
						
							|  |  |  |             return orig_attr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self._async_mode: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def _wrapped(*args, **kwargs): | 
					
						
							| 
									
										
										
										
											2025-07-16 08:11:08 +08:00
										 |  |  |                 for i in range(REDIS_SENTINEL_MAX_RETRY_COUNT): | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |                     try: | 
					
						
							|  |  |  |                         method = getattr(self._master(), item) | 
					
						
							|  |  |  |                         result = method(*args, **kwargs) | 
					
						
							|  |  |  |                         if inspect.iscoroutine(result): | 
					
						
							|  |  |  |                             return await result | 
					
						
							|  |  |  |                         return result | 
					
						
							|  |  |  |                     except ( | 
					
						
							|  |  |  |                         redis.exceptions.ConnectionError, | 
					
						
							|  |  |  |                         redis.exceptions.ReadOnlyError, | 
					
						
							|  |  |  |                     ) as e: | 
					
						
							| 
									
										
										
										
											2025-07-16 08:11:08 +08:00
										 |  |  |                         if i < REDIS_SENTINEL_MAX_RETRY_COUNT - 1: | 
					
						
							| 
									
										
										
										
											2025-07-16 08:16:11 +08:00
										 |  |  |                             log.debug( | 
					
						
							|  |  |  |                                 "Redis sentinel fail-over (%s). Retry %s/%s", | 
					
						
							|  |  |  |                                 type(e).__name__, | 
					
						
							|  |  |  |                                 i + 1, | 
					
						
							|  |  |  |                                 REDIS_SENTINEL_MAX_RETRY_COUNT, | 
					
						
							|  |  |  |                             ) | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |                             continue | 
					
						
							| 
									
										
										
										
											2025-07-16 08:16:11 +08:00
										 |  |  |                         log.error( | 
					
						
							|  |  |  |                             "Redis operation failed after %s retries: %s", | 
					
						
							|  |  |  |                             REDIS_SENTINEL_MAX_RETRY_COUNT, | 
					
						
							|  |  |  |                             e, | 
					
						
							|  |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |                         raise e from e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return _wrapped | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def _wrapped(*args, **kwargs): | 
					
						
							| 
									
										
										
										
											2025-07-16 08:11:08 +08:00
										 |  |  |                 for i in range(REDIS_SENTINEL_MAX_RETRY_COUNT): | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |                     try: | 
					
						
							|  |  |  |                         method = getattr(self._master(), item) | 
					
						
							|  |  |  |                         return method(*args, **kwargs) | 
					
						
							|  |  |  |                     except ( | 
					
						
							|  |  |  |                         redis.exceptions.ConnectionError, | 
					
						
							|  |  |  |                         redis.exceptions.ReadOnlyError, | 
					
						
							|  |  |  |                     ) as e: | 
					
						
							| 
									
										
										
										
											2025-07-16 08:11:08 +08:00
										 |  |  |                         if i < REDIS_SENTINEL_MAX_RETRY_COUNT - 1: | 
					
						
							| 
									
										
										
										
											2025-07-16 08:16:11 +08:00
										 |  |  |                             log.debug( | 
					
						
							|  |  |  |                                 "Redis sentinel fail-over (%s). Retry %s/%s", | 
					
						
							|  |  |  |                                 type(e).__name__, | 
					
						
							|  |  |  |                                 i + 1, | 
					
						
							|  |  |  |                                 REDIS_SENTINEL_MAX_RETRY_COUNT, | 
					
						
							|  |  |  |                             ) | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |                             continue | 
					
						
							| 
									
										
										
										
											2025-07-16 08:16:11 +08:00
										 |  |  |                         log.error( | 
					
						
							|  |  |  |                             "Redis operation failed after %s retries: %s", | 
					
						
							|  |  |  |                             REDIS_SENTINEL_MAX_RETRY_COUNT, | 
					
						
							|  |  |  |                             e, | 
					
						
							|  |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |                         raise e from e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return _wrapped | 
					
						
							| 
									
										
										
										
											2025-03-18 15:25:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 02:47:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 03:50:00 +08:00
										 |  |  | def parse_redis_service_url(redis_url): | 
					
						
							| 
									
										
										
										
											2025-03-18 15:25:31 +08:00
										 |  |  |     parsed_url = urlparse(redis_url) | 
					
						
							|  |  |  |     if parsed_url.scheme != "redis": | 
					
						
							|  |  |  |         raise ValueError("Invalid Redis URL scheme. Must be 'redis'.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |         "username": parsed_url.username or None, | 
					
						
							|  |  |  |         "password": parsed_url.password or None, | 
					
						
							| 
									
										
										
										
											2025-03-29 02:47:14 +08:00
										 |  |  |         "service": parsed_url.hostname or "mymaster", | 
					
						
							| 
									
										
										
										
											2025-03-18 15:25:31 +08:00
										 |  |  |         "port": parsed_url.port or 6379, | 
					
						
							|  |  |  |         "db": int(parsed_url.path.lstrip("/") or 0), | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 02:47:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-09 00:58:31 +08:00
										 |  |  | def get_redis_connection( | 
					
						
							| 
									
										
										
										
											2025-06-09 19:37:05 +08:00
										 |  |  |     redis_url, redis_sentinels, async_mode=False, decode_responses=True | 
					
						
							|  |  |  | ): | 
					
						
							|  |  |  |     if async_mode: | 
					
						
							|  |  |  |         import redis.asyncio as redis | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If using sentinel in async mode | 
					
						
							|  |  |  |         if redis_sentinels: | 
					
						
							|  |  |  |             redis_config = parse_redis_service_url(redis_url) | 
					
						
							|  |  |  |             sentinel = redis.sentinel.Sentinel( | 
					
						
							|  |  |  |                 redis_sentinels, | 
					
						
							|  |  |  |                 port=redis_config["port"], | 
					
						
							|  |  |  |                 db=redis_config["db"], | 
					
						
							|  |  |  |                 username=redis_config["username"], | 
					
						
							|  |  |  |                 password=redis_config["password"], | 
					
						
							|  |  |  |                 decode_responses=decode_responses, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |             return SentinelRedisProxy( | 
					
						
							|  |  |  |                 sentinel, | 
					
						
							|  |  |  |                 redis_config["service"], | 
					
						
							|  |  |  |                 async_mode=async_mode, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2025-06-09 19:37:05 +08:00
										 |  |  |         elif redis_url: | 
					
						
							|  |  |  |             return redis.from_url(redis_url, decode_responses=decode_responses) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return None | 
					
						
							| 
									
										
										
										
											2025-06-09 00:58:31 +08:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2025-06-09 19:37:05 +08:00
										 |  |  |         import redis | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if redis_sentinels: | 
					
						
							|  |  |  |             redis_config = parse_redis_service_url(redis_url) | 
					
						
							|  |  |  |             sentinel = redis.sentinel.Sentinel( | 
					
						
							|  |  |  |                 redis_sentinels, | 
					
						
							|  |  |  |                 port=redis_config["port"], | 
					
						
							|  |  |  |                 db=redis_config["db"], | 
					
						
							|  |  |  |                 username=redis_config["username"], | 
					
						
							|  |  |  |                 password=redis_config["password"], | 
					
						
							|  |  |  |                 decode_responses=decode_responses, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2025-07-15 09:37:04 +08:00
										 |  |  |             return SentinelRedisProxy( | 
					
						
							|  |  |  |                 sentinel, | 
					
						
							|  |  |  |                 redis_config["service"], | 
					
						
							|  |  |  |                 async_mode=async_mode, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2025-06-09 19:37:05 +08:00
										 |  |  |         elif redis_url: | 
					
						
							|  |  |  |             return redis.Redis.from_url(redis_url, decode_responses=decode_responses) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return None | 
					
						
							| 
									
										
										
										
											2025-03-18 16:28:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 02:47:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-27 15:51:55 +08:00
										 |  |  | def get_sentinels_from_env(sentinel_hosts_env, sentinel_port_env): | 
					
						
							| 
									
										
										
										
											2025-03-27 17:22:49 +08:00
										 |  |  |     if sentinel_hosts_env: | 
					
						
							| 
									
										
										
										
											2025-03-29 02:47:14 +08:00
										 |  |  |         sentinel_hosts = sentinel_hosts_env.split(",") | 
					
						
							|  |  |  |         sentinel_port = int(sentinel_port_env) | 
					
						
							| 
									
										
										
										
											2025-03-27 17:22:49 +08:00
										 |  |  |         return [(host, sentinel_port) for host in sentinel_hosts] | 
					
						
							|  |  |  |     return [] | 
					
						
							| 
									
										
										
										
											2025-03-18 16:28:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-29 02:47:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-03 03:50:00 +08:00
										 |  |  | def get_sentinel_url_from_env(redis_url, sentinel_hosts_env, sentinel_port_env): | 
					
						
							|  |  |  |     redis_config = parse_redis_service_url(redis_url) | 
					
						
							|  |  |  |     username = redis_config["username"] or "" | 
					
						
							|  |  |  |     password = redis_config["password"] or "" | 
					
						
							| 
									
										
										
										
											2025-04-03 14:24:24 +08:00
										 |  |  |     auth_part = "" | 
					
						
							|  |  |  |     if username or password: | 
					
						
							|  |  |  |         auth_part = f"{username}:{password}@" | 
					
						
							| 
									
										
										
										
											2025-04-13 07:35:11 +08:00
										 |  |  |     hosts_part = ",".join( | 
					
						
							|  |  |  |         f"{host}:{sentinel_port_env}" for host in sentinel_hosts_env.split(",") | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2025-04-03 14:24:24 +08:00
										 |  |  |     return f"redis+sentinel://{auth_part}{hosts_part}/{redis_config['db']}/{redis_config['service']}" |