Update requirements.txt to use version 1.5.3 of sientia-dataops-library and enhance DataManager and ResourceManager with improved logging and timestamping for data insertion and retrieval.
316 lines
12 KiB
Python
316 lines
12 KiB
Python
from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler
|
|
from sientia_do.observability.logger import Logger
|
|
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(SientiaMonitoring):
|
|
"""
|
|
Manages Redis-based resource coordination and slot leasing for the OPC Ingestor.
|
|
|
|
The ResourceManager is responsible for:
|
|
- Coordinating slot allocation across multiple ingestor instances
|
|
- Managing lease lifecycles and heartbeats for load balancing
|
|
- Providing distributed locking and resource management
|
|
- Monitoring Redis operations and connection health
|
|
|
|
The manager implements a sophisticated slot leasing system that enables:
|
|
- Dynamic load distribution across multiple ingestor instances
|
|
- Automatic failover and recovery from instance failures
|
|
- Fair resource allocation based on system capacity
|
|
- Real-time monitoring of system health and performance
|
|
|
|
Args:
|
|
host (str): Redis server hostname
|
|
port (int): Redis server port
|
|
lease_ttl (int): Time-to-live for slot leases in seconds
|
|
heartbeat_ttl (int): Time-to-live for heartbeat signals in seconds
|
|
metadata (dict): Application metadata for notifications and tracking
|
|
logger (Logger): Logger instance for application logging
|
|
notification_handler (NotificationHandler): Handler for sending notifications
|
|
username (str, optional): Redis username for authentication
|
|
password (str, optional): Redis password for authentication
|
|
|
|
Attributes:
|
|
redis (Redis): Redis client instance
|
|
lease_ttl (int): Time-to-live for slot leases
|
|
heartbeat_ttl (int): Time-to-live for heartbeat signals
|
|
metadata (dict): Application metadata
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
host: str,
|
|
port: int,
|
|
lease_ttl: int,
|
|
heartbeat_ttl: int,
|
|
metadata: dict,
|
|
logger: Logger,
|
|
notification_handler: NotificationHandler,
|
|
metrics_controller: MetricsController,
|
|
username: str | None = None,
|
|
password: str | None = None,
|
|
) -> None:
|
|
"""
|
|
Initializes the ResourceManager with Redis connection and configuration.
|
|
|
|
This constructor establishes a connection to Redis and verifies connectivity
|
|
by performing a ping operation. It sets up the connection with optional
|
|
authentication and records the connection status in metrics.
|
|
|
|
Args:
|
|
host (str): Redis server hostname
|
|
port (int): Redis server port
|
|
lease_ttl (int): Time-to-live for slot leases in seconds
|
|
heartbeat_ttl (int): Time-to-live for heartbeat signals in seconds
|
|
metadata (dict): Application metadata
|
|
logger (Logger): Logger instance
|
|
notification_handler (NotificationHandler): Notification handler
|
|
username (str, optional): Redis username for authentication
|
|
password (str, optional): Redis password for authentication
|
|
|
|
Raises:
|
|
Exception: If Redis connection fails, the error is logged and metrics
|
|
are updated before re-raising the exception.
|
|
|
|
Metrics:
|
|
- REDIS_CONNECTION_STATUS: Set to 1 on successful connection, 0 on failure
|
|
"""
|
|
SientiaMonitoring.__init__(
|
|
self,
|
|
logger=logger,
|
|
notification_handler=notification_handler,
|
|
metrics_controller=metrics_controller,
|
|
)
|
|
|
|
try:
|
|
self.redis_repository = RedisRepository(
|
|
host=host,
|
|
port=port,
|
|
username=username,
|
|
password=password,
|
|
logger=logger,
|
|
notification_handler=notification_handler,
|
|
metrics_controller=metrics_controller,
|
|
)
|
|
self.redis_repository.redis_client.ping()
|
|
except Exception as e:
|
|
self.logger.error(f'Failed to connect to Redis: {e}')
|
|
raise
|
|
|
|
self.lease_ttl = lease_ttl
|
|
self.heartbeat_ttl = heartbeat_ttl
|
|
self.metadata = metadata
|
|
|
|
async def get_tag_slot(self, tag_id: str) -> dict | None:
|
|
"""
|
|
Retrieve the tag slot information for a given ID.
|
|
|
|
This method constructs the Redis key for a tag slot and retrieves
|
|
the associated configuration information.
|
|
|
|
Args:
|
|
id (str): The unique identifier of the tag slot to retrieve.
|
|
|
|
Returns:
|
|
dict: A dictionary containing the tag slot information associated
|
|
with the given ID, or None if not found.
|
|
|
|
The method constructs the key using the pattern "slot:opc_tags:{id}"
|
|
and delegates to the get() method for the actual Redis operation.
|
|
"""
|
|
|
|
self.info(f'Getting tag slot for tag_id: {tag_id}', metadata=self.metadata)
|
|
|
|
slot = await self.redis_repository.get(f'slot:opc_tags:{tag_id}', metadata=self.metadata)
|
|
self.info(f'Tag slot for tag_id: {tag_id} is: {slot}', metadata=self.metadata)
|
|
return slot
|
|
|
|
async def ingestor_heartbeat(self) -> None:
|
|
"""
|
|
Sends a heartbeat signal to Redis to indicate that the ingestor is active.
|
|
|
|
This method sets a key in Redis with a specific format that includes the
|
|
ingestor's pod ID. The key is set with a value of 1 and an expiration
|
|
time defined by `self.heartbeat_ttl`. This allows monitoring systems to
|
|
track the activity and health of the ingestor.
|
|
|
|
The heartbeat mechanism enables:
|
|
- Load balancers to identify active ingestor instances
|
|
- Health monitoring systems to detect failed instances
|
|
- Automatic failover and recovery mechanisms
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="set"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for heartbeat operations
|
|
"""
|
|
|
|
await self.redis_repository.set(
|
|
f'heartbeat:ingestor:{self.pod_id}', 1, ttl=self.heartbeat_ttl, metadata=self.metadata
|
|
)
|
|
|
|
async def lease_tag(self, tag_id: str) -> bool:
|
|
"""
|
|
Attempts to lease a tag by setting a key in Redis with a specified TTL.
|
|
|
|
This method uses the Redis `SET` command with the `NX` option to ensure that
|
|
the key is only set if it does not already exist. The key is set with an
|
|
expiration time defined by `lease_ttl`. This implements a distributed
|
|
locking mechanism for tag allocation.
|
|
|
|
Args:
|
|
tag_id (str): The unique identifier of the tag to be leased.
|
|
|
|
Returns:
|
|
bool: True if the lease was successfully acquired, False if the tag
|
|
is already leased by another ingestor.
|
|
|
|
The leasing mechanism ensures:
|
|
- Only one ingestor can process a specific tag at a time
|
|
- Automatic lease expiration prevents deadlocks
|
|
- Fair distribution of tags across available ingestor instances
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="set_nx"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for lease operations
|
|
"""
|
|
|
|
return await self.redis_repository.set(
|
|
f'lease:opc_tags:{tag_id}',
|
|
self.pod_id,
|
|
ttl=self.lease_ttl,
|
|
nx=True,
|
|
metadata=self.metadata,
|
|
)
|
|
|
|
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.
|
|
|
|
This method checks if the current pod (identified by `self.pod_id`) holds
|
|
the lease for the given OPC tag. If so, it extends the lease by resetting
|
|
its expiration time in Redis to the configured lease TTL.
|
|
|
|
Args:
|
|
tag_id (str): The identifier of the OPC tag whose lease is to be renewed.
|
|
|
|
Returns:
|
|
bool: True if the lease was successfully renewed, False if the current
|
|
pod doesn't hold the lease or renewal failed.
|
|
|
|
Lease renewal is essential for:
|
|
- Maintaining continuous tag processing without interruptions
|
|
- Preventing lease expiration during long-running operations
|
|
- Ensuring system stability and reliability
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="get" and "expire"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for renewal operations
|
|
"""
|
|
|
|
current = await self.redis_repository.get(
|
|
f'lease:opc_tags:{tag_id}', metadata=self.metadata
|
|
)
|
|
if current == self.pod_id:
|
|
await self.redis_repository.expire(
|
|
f'lease:opc_tags:{tag_id}', self.lease_ttl, metadata=self.metadata
|
|
)
|
|
return True
|
|
return False
|
|
|
|
async def drop_tag_lease(self, tag_id: str) -> None:
|
|
"""
|
|
Drops the lease for a specific OPC tag.
|
|
|
|
This method removes the lease for the given OPC tag by deleting the
|
|
corresponding key in Redis. This is typically called when an ingestor
|
|
is shutting down or when it needs to release a tag for reallocation.
|
|
|
|
Args:
|
|
tag_id (str): The identifier of the OPC tag whose lease is to be dropped.
|
|
|
|
Lease dropping enables:
|
|
- Graceful shutdown of ingestor instances
|
|
- Dynamic reallocation of tags for load balancing
|
|
- Recovery from failed or unresponsive ingestor instances
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="delete"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for lease dropping operations
|
|
"""
|
|
|
|
await self.redis_repository.delete(f'lease:opc_tags:{tag_id}', metadata=self.metadata)
|
|
|
|
async def get_all_ingestors(self) -> list[str]:
|
|
"""
|
|
Retrieves all active ingestors from Redis.
|
|
|
|
This method fetches all keys in Redis that match the pattern for ingestor
|
|
heartbeats and returns a list of active ingestor identifiers. The method
|
|
uses the pattern "heartbeat:ingestor:*" to find all active instances.
|
|
|
|
Returns:
|
|
List[str]: A list of active ingestor identifiers, extracted from
|
|
the Redis keys by removing the "heartbeat:ingestor:" prefix.
|
|
|
|
This information is used for:
|
|
- Load balancing calculations
|
|
- System health monitoring
|
|
- Resource allocation decisions
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="keys"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for ingestor discovery
|
|
"""
|
|
|
|
return await self.redis_repository.keys('heartbeat:ingestor:*', metadata=self.metadata)
|
|
|
|
async def get_all_slots(self) -> list[str]:
|
|
"""
|
|
Retrieves all available slots from Redis.
|
|
|
|
This method fetches all keys in Redis that match the pattern for OPC tag
|
|
slots and returns a list of slot identifiers. The method uses the pattern
|
|
"slot:opc_tags:*" to find all configured slots.
|
|
|
|
Returns:
|
|
List[str]: A list of slot identifiers, extracted from the Redis keys
|
|
by removing the "slot:opc_tags:" prefix.
|
|
|
|
Slot information is used for:
|
|
- Resource allocation planning
|
|
- Load balancing across ingestor instances
|
|
- System capacity monitoring
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="keys"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for slot discovery
|
|
"""
|
|
|
|
return await self.redis_repository.keys('slot:opc_tags:*', metadata=self.metadata)
|
|
|
|
async def get_all_leases(self) -> list[str]:
|
|
"""
|
|
Retrieves all active leases from Redis.
|
|
|
|
This method fetches all keys in Redis that match the pattern for OPC tag
|
|
leases and returns a list of lease identifiers. The method uses the pattern
|
|
"lease:opc_tags:*" to find all active leases.
|
|
|
|
Returns:
|
|
List[str]: A list of lease identifiers, extracted from the Redis keys
|
|
by removing the "lease:opc_tags:" prefix.
|
|
|
|
Lease information is used for:
|
|
- Current resource utilization monitoring
|
|
- Load balancing calculations
|
|
- System health and performance analysis
|
|
|
|
Metrics:
|
|
- REDIS_OPERATIONS_TOTAL: Incremented with operation="keys"
|
|
- REDIS_OPERATIONS_DURATION: Records timing for lease discovery
|
|
"""
|
|
|
|
return await self.redis_repository.keys('lease:opc_tags:*', metadata=self.metadata)
|