SIENTIAPDE-1325
SIENTIAPDE-1325: Refactor Ingestor and Manager Classes for Enhanced Asynchronous Operations - Introduced asynchronous methods across Ingestor, IngestorManager, DataManager, and OpcManager to improve performance and responsiveness. - Integrated MetricsController into various classes for better observability and monitoring. - Updated Redis and MongoDB interactions to support asynchronous operations, enhancing data handling efficiency. - Removed deprecated Redis metrics and streamlined resource management logic. - Adjusted unit tests to accommodate the new asynchronous behavior and ensure proper mocking of async methods.
This commit is contained in:
@@ -1,16 +1,11 @@
|
||||
import json
|
||||
from time import time
|
||||
|
||||
from redis import Redis
|
||||
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
||||
from sientia_do.notifications.models import NotificationLevel
|
||||
from sientia_do.observability.logger import Logger
|
||||
from sientia_do.temporal.activities.base import BaseActivity
|
||||
|
||||
import ingestor.metrics as metrics
|
||||
from sientia_do.observability.metrics_controller import MetricsController
|
||||
from sientia_do.observability.sientia_monitoring import SientiaMonitoring
|
||||
from sientia_do.repository.redis_repository import RedisRepository
|
||||
|
||||
|
||||
class ResourceManager(BaseActivity):
|
||||
class ResourceManager(SientiaMonitoring):
|
||||
"""
|
||||
Manages Redis-based resource coordination and slot leasing for the OPC Ingestor.
|
||||
|
||||
@@ -53,6 +48,7 @@ class ResourceManager(BaseActivity):
|
||||
metadata: dict,
|
||||
logger: Logger,
|
||||
notification_handler: NotificationHandler,
|
||||
metrics_controller: MetricsController,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
) -> None:
|
||||
@@ -81,102 +77,34 @@ class ResourceManager(BaseActivity):
|
||||
Metrics:
|
||||
- REDIS_CONNECTION_STATUS: Set to 1 on successful connection, 0 on failure
|
||||
"""
|
||||
BaseActivity.__init__(
|
||||
self, logger=logger, notification_handler=notification_handler, set_error_counter=True
|
||||
SientiaMonitoring.__init__(
|
||||
self,
|
||||
logger=logger,
|
||||
notification_handler=notification_handler,
|
||||
metrics_controller=metrics_controller,
|
||||
set_error_counter=True,
|
||||
)
|
||||
|
||||
try:
|
||||
self.redis = Redis(
|
||||
self.redis_repository = RedisRepository(
|
||||
host=host,
|
||||
port=port,
|
||||
decode_responses=True,
|
||||
username=username,
|
||||
password=password,
|
||||
logger=logger,
|
||||
notification_handler=notification_handler,
|
||||
metrics_controller=metrics_controller,
|
||||
)
|
||||
self.redis.ping()
|
||||
metrics.REDIS_CONNECTION_STATUS.labels(pod_id=self.pod_id).set(1)
|
||||
self.redis_repository.redis_client.ping()
|
||||
except Exception as e:
|
||||
self.logger.error(f'Failed to connect to Redis: {e}')
|
||||
metrics.REDIS_CONNECTION_STATUS.labels(pod_id=self.pod_id).set(0)
|
||||
raise
|
||||
|
||||
self.lease_ttl = lease_ttl
|
||||
self.heartbeat_ttl = heartbeat_ttl
|
||||
self.metadata = metadata
|
||||
|
||||
def _execute_redis_op(self, operation_name: str, func, *args, **kwargs):
|
||||
"""
|
||||
Wrapper to execute Redis operations and record metrics.
|
||||
|
||||
This method provides a unified interface for Redis operations that:
|
||||
- Records operation timing and success/failure metrics
|
||||
- Handles error notifications consistently
|
||||
- Ensures all Redis operations are properly monitored
|
||||
|
||||
Args:
|
||||
operation_name (str): Name of the Redis operation for metrics labeling
|
||||
func: The Redis function to execute
|
||||
*args: Positional arguments for the Redis function
|
||||
**kwargs: Keyword arguments for the Redis function
|
||||
|
||||
Returns:
|
||||
The result of the Redis operation
|
||||
|
||||
Raises:
|
||||
Exception: Re-raises any exception from the Redis operation after
|
||||
recording error metrics and sending notifications.
|
||||
|
||||
Metrics:
|
||||
- REDIS_OPERATIONS_TOTAL: Incremented on successful operations
|
||||
- REDIS_OPERATIONS_DURATION: Records operation timing
|
||||
- REDIS_OPERATIONS_ERRORS: Incremented on operation failures
|
||||
"""
|
||||
start_time = time()
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
metrics.REDIS_OPERATIONS_TOTAL.labels(
|
||||
pod_id=self.pod_id, operation=operation_name
|
||||
).inc()
|
||||
duration = time() - start_time
|
||||
metrics.REDIS_OPERATIONS_DURATION.labels(
|
||||
pod_id=self.pod_id, operation=operation_name
|
||||
).observe(duration)
|
||||
return result
|
||||
except Exception as e:
|
||||
metrics.REDIS_OPERATIONS_ERRORS.labels(
|
||||
pod_id=self.pod_id, operation=operation_name
|
||||
).inc()
|
||||
self.send_notification(
|
||||
metadata=self.metadata,
|
||||
notification_id=f'REDIS_OPERATION_ERROR_{operation_name}',
|
||||
message=f"Error in Redis operation '{operation_name}': {e}",
|
||||
block='redis_manager',
|
||||
level=NotificationLevel.ERROR,
|
||||
)
|
||||
raise
|
||||
|
||||
def get(self, key: str) -> dict | None:
|
||||
"""
|
||||
Retrieve a value from Redis by its key and return it as a dictionary.
|
||||
|
||||
This method fetches a value from Redis and attempts to parse it as JSON.
|
||||
If the key doesn't exist or the value is empty, it returns None.
|
||||
|
||||
Args:
|
||||
key (str): The key to look up in Redis.
|
||||
|
||||
Returns:
|
||||
dict: The value associated with the key, parsed as a dictionary,
|
||||
or None if the key does not exist or the value is empty.
|
||||
|
||||
Metrics:
|
||||
- REDIS_OPERATIONS_TOTAL: Incremented with operation="get"
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for get operations
|
||||
"""
|
||||
|
||||
history = self._execute_redis_op('get', self.redis.get, key)
|
||||
return json.loads(history) if history else None
|
||||
|
||||
def get_tag_slot(self, tag_id: str) -> dict | None:
|
||||
async def get_tag_slot(self, tag_id: str) -> dict | None:
|
||||
"""
|
||||
Retrieve the tag slot information for a given ID.
|
||||
|
||||
@@ -194,9 +122,9 @@ class ResourceManager(BaseActivity):
|
||||
and delegates to the get() method for the actual Redis operation.
|
||||
"""
|
||||
|
||||
return self.get(f'slot:opc_tags:{tag_id}')
|
||||
return await self.redis_repository.get(f'slot:opc_tags:{tag_id}', metadata=self.metadata)
|
||||
|
||||
def ingestor_heartbeat(self) -> None:
|
||||
async def ingestor_heartbeat(self) -> None:
|
||||
"""
|
||||
Sends a heartbeat signal to Redis to indicate that the ingestor is active.
|
||||
|
||||
@@ -215,15 +143,11 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for heartbeat operations
|
||||
"""
|
||||
|
||||
self._execute_redis_op(
|
||||
'set',
|
||||
self.redis.set,
|
||||
f'heartbeat:ingestor:{self.pod_id}',
|
||||
1,
|
||||
ex=self.heartbeat_ttl,
|
||||
await self.redis_repository.set(
|
||||
f'heartbeat:ingestor:{self.pod_id}', 1, ttl=self.heartbeat_ttl, metadata=self.metadata
|
||||
)
|
||||
|
||||
def lease_tag(self, tag_id: str) -> bool:
|
||||
async def lease_tag(self, tag_id: str) -> bool:
|
||||
"""
|
||||
Attempts to lease a tag by setting a key in Redis with a specified TTL.
|
||||
|
||||
@@ -249,16 +173,15 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for lease operations
|
||||
"""
|
||||
|
||||
return self._execute_redis_op(
|
||||
'set_nx',
|
||||
self.redis.set,
|
||||
return await self.redis_repository.set(
|
||||
f'lease:opc_tags:{tag_id}',
|
||||
self.pod_id,
|
||||
ttl=self.lease_ttl,
|
||||
nx=True,
|
||||
ex=self.lease_ttl,
|
||||
metadata=self.metadata,
|
||||
)
|
||||
|
||||
def renew_tag_lease(self, tag_id: str) -> bool:
|
||||
async def renew_tag_lease(self, tag_id: str) -> bool:
|
||||
"""
|
||||
Renews the lease for a specific OPC tag if the current pod holds the lease.
|
||||
|
||||
@@ -283,15 +206,17 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for renewal operations
|
||||
"""
|
||||
|
||||
current = self._execute_redis_op('get', self.redis.get, f'lease:opc_tags:{tag_id}')
|
||||
current = await self.redis_repository.get(
|
||||
f'lease:opc_tags:{tag_id}', metadata=self.metadata
|
||||
)
|
||||
if current == self.pod_id:
|
||||
self._execute_redis_op(
|
||||
'expire', self.redis.expire, f'lease:opc_tags:{tag_id}', self.lease_ttl
|
||||
await self.redis_repository.expire(
|
||||
f'lease:opc_tags:{tag_id}', self.lease_ttl, metadata=self.metadata
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def drop_tag_lease(self, tag_id: str) -> None:
|
||||
async def drop_tag_lease(self, tag_id: str) -> None:
|
||||
"""
|
||||
Drops the lease for a specific OPC tag.
|
||||
|
||||
@@ -312,9 +237,9 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for lease dropping operations
|
||||
"""
|
||||
|
||||
self._execute_redis_op('delete', self.redis.delete, f'lease:opc_tags:{tag_id}')
|
||||
await self.redis_repository.delete(f'lease:opc_tags:{tag_id}', metadata=self.metadata)
|
||||
|
||||
def get_all_ingestors(self) -> list[str]:
|
||||
async def get_all_ingestors(self) -> list[str]:
|
||||
"""
|
||||
Retrieves all active ingestors from Redis.
|
||||
|
||||
@@ -336,9 +261,9 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for ingestor discovery
|
||||
"""
|
||||
|
||||
return self._execute_redis_op('keys', self.redis.keys, 'heartbeat:ingestor:*')
|
||||
return await self.redis_repository.keys('heartbeat:ingestor:*', metadata=self.metadata)
|
||||
|
||||
def get_all_slots(self) -> list[str]:
|
||||
async def get_all_slots(self) -> list[str]:
|
||||
"""
|
||||
Retrieves all available slots from Redis.
|
||||
|
||||
@@ -360,9 +285,9 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for slot discovery
|
||||
"""
|
||||
|
||||
return self._execute_redis_op('keys', self.redis.keys, 'slot:opc_tags:*')
|
||||
return await self.redis_repository.keys('slot:opc_tags:*', metadata=self.metadata)
|
||||
|
||||
def get_all_leases(self) -> list[str]:
|
||||
async def get_all_leases(self) -> list[str]:
|
||||
"""
|
||||
Retrieves all active leases from Redis.
|
||||
|
||||
@@ -384,4 +309,4 @@ class ResourceManager(BaseActivity):
|
||||
- REDIS_OPERATIONS_DURATION: Records timing for lease discovery
|
||||
"""
|
||||
|
||||
return self._execute_redis_op('keys', self.redis.keys, 'lease:opc_tags:*')
|
||||
return await self.redis_repository.keys('lease:opc_tags:*', metadata=self.metadata)
|
||||
|
||||
Reference in New Issue
Block a user