nebu 0.1.117__tar.gz → 0.1.118__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nebu-0.1.117/src/nebu.egg-info → nebu-0.1.118}/PKG-INFO +1 -1
- {nebu-0.1.117 → nebu-0.1.118}/pyproject.toml +1 -1
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/consumer_health_worker.py +44 -4
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/models.py +6 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/processor.py +53 -30
- {nebu-0.1.117 → nebu-0.1.118/src/nebu.egg-info}/PKG-INFO +1 -1
- {nebu-0.1.117 → nebu-0.1.118}/LICENSE +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/README.md +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/setup.cfg +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/__init__.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/auth.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/builders/builder.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/builders/models.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/cache.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/config.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/containers/container.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/containers/models.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/data.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/errors.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/logging.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/meta.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/namespaces/models.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/namespaces/namespace.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/orign.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/consumer.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/consumer_process_worker.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/decorate.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/processors/default.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/redis/models.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu/services/service.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu.egg-info/SOURCES.txt +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu.egg-info/dependency_links.txt +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu.egg-info/requires.txt +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/src/nebu.egg-info/top_level.txt +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/tests/test_bucket.py +0 -0
- {nebu-0.1.117 → nebu-0.1.118}/tests/test_containers.py +0 -0
@@ -5,13 +5,16 @@ import os
|
|
5
5
|
import socket
|
6
6
|
import sys
|
7
7
|
import time
|
8
|
-
from typing import Dict, List, Optional, Tuple, cast
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, cast
|
9
9
|
|
10
10
|
import redis
|
11
11
|
import socks
|
12
12
|
from redis import ConnectionError, ResponseError
|
13
13
|
from redis.exceptions import TimeoutError as RedisTimeoutError
|
14
14
|
|
15
|
+
# Assuming these are imported from other modules
|
16
|
+
from nebu.processors.models import V1ProcessorHealthResponse
|
17
|
+
|
15
18
|
|
16
19
|
def setup_health_logging():
|
17
20
|
"""Set up logging for the health check worker to write to a dedicated file."""
|
@@ -50,16 +53,53 @@ def process_health_check_message(
|
|
50
53
|
"""Processes a single health check message."""
|
51
54
|
logger.info(f"Processing health check message {message_id}: {message_data}")
|
52
55
|
|
53
|
-
|
56
|
+
health_status = "ok"
|
57
|
+
health_message: Optional[str] = "Health check processed successfully."
|
58
|
+
details: Optional[Dict[str, Any]] = None
|
59
|
+
return_stream: Optional[str] = None
|
60
|
+
|
54
61
|
try:
|
55
62
|
if "data" in message_data:
|
56
63
|
data = json.loads(message_data["data"])
|
57
64
|
logger.info(f"Health check data: {data}")
|
65
|
+
# Example: Extract return_stream if present in the health check data
|
66
|
+
return_stream = data.get("return_stream")
|
67
|
+
# Example: Update details if provided
|
68
|
+
if "check_details" in data:
|
69
|
+
details = {"processed_details": data["check_details"]}
|
70
|
+
|
58
71
|
except (json.JSONDecodeError, KeyError) as e:
|
59
72
|
logger.warning(f"Could not parse health check message data: {e}")
|
73
|
+
health_status = "error"
|
74
|
+
health_message = f"Failed to parse health check message data: {e}"
|
75
|
+
details = {"error": str(e)}
|
76
|
+
|
77
|
+
# Construct the health response
|
78
|
+
health_response = V1ProcessorHealthResponse(
|
79
|
+
status=health_status, message=health_message, details=details
|
80
|
+
)
|
81
|
+
|
82
|
+
logger.info(
|
83
|
+
f"Health response for message {message_id}: {health_response.model_dump_json()}"
|
84
|
+
)
|
60
85
|
|
61
|
-
#
|
62
|
-
|
86
|
+
# If a return_stream is specified, send the response there
|
87
|
+
if return_stream:
|
88
|
+
try:
|
89
|
+
# It's good practice to set a maxlen for the return stream to prevent it from growing indefinitely
|
90
|
+
redis_conn.xadd(
|
91
|
+
return_stream,
|
92
|
+
health_response.model_dump(), # type: ignore[arg-type]
|
93
|
+
maxlen=1000,
|
94
|
+
approximate=True,
|
95
|
+
)
|
96
|
+
logger.info(
|
97
|
+
f"Sent health response for {message_id} to stream: {return_stream}"
|
98
|
+
)
|
99
|
+
except Exception as e_resp_send:
|
100
|
+
logger.error(
|
101
|
+
f"Failed to send health response for {message_id} to stream {return_stream}: {e_resp_send}"
|
102
|
+
)
|
63
103
|
|
64
104
|
# Acknowledge the health check message
|
65
105
|
try:
|
@@ -149,3 +149,9 @@ class V1OpenAIStreamResponse(BaseModel):
|
|
149
149
|
content: Any # Using Any for ChatCompletionResponse
|
150
150
|
created_at: int
|
151
151
|
user_id: Optional[str] = None
|
152
|
+
|
153
|
+
|
154
|
+
class V1ProcessorHealthResponse(BaseModel):
|
155
|
+
status: str
|
156
|
+
message: Optional[str] = None
|
157
|
+
details: Optional[Any] = None
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import json
|
2
2
|
import threading
|
3
3
|
import time
|
4
|
-
|
4
|
+
|
5
|
+
# import uuid # Removed unused import
|
5
6
|
from typing import (
|
6
7
|
Any,
|
7
8
|
Dict,
|
@@ -23,6 +24,7 @@ from nebu.meta import V1ResourceMetaRequest, V1ResourceReference
|
|
23
24
|
from nebu.processors.models import (
|
24
25
|
V1ContainerRequest,
|
25
26
|
V1Processor,
|
27
|
+
V1ProcessorHealthResponse,
|
26
28
|
V1ProcessorRequest,
|
27
29
|
V1Processors,
|
28
30
|
V1ProcessorScaleRequest,
|
@@ -246,7 +248,7 @@ class Processor(Generic[InputType, OutputType]):
|
|
246
248
|
|
247
249
|
# --- Wait for health check if requested ---
|
248
250
|
if wait_for_healthy:
|
249
|
-
self.
|
251
|
+
self.wait_for_healthy()
|
250
252
|
|
251
253
|
def __call__(
|
252
254
|
self,
|
@@ -559,11 +561,11 @@ class Processor(Generic[InputType, OutputType]):
|
|
559
561
|
else:
|
560
562
|
logger.info(f"No active log stream to stop for {self.name}.")
|
561
563
|
|
562
|
-
def
|
564
|
+
def wait_for_healthy(
|
563
565
|
self, timeout: float = 3600.0, retry_interval: float = 5.0
|
564
566
|
) -> None:
|
565
567
|
"""
|
566
|
-
Wait for the processor to respond to health checks.
|
568
|
+
Wait for the processor to respond to health checks using the health endpoint.
|
567
569
|
|
568
570
|
Args:
|
569
571
|
timeout: Maximum time to wait for health check in seconds
|
@@ -573,40 +575,25 @@ class Processor(Generic[InputType, OutputType]):
|
|
573
575
|
raise ValueError("Processor not found, cannot perform health check")
|
574
576
|
|
575
577
|
logger.info(
|
576
|
-
f"Waiting for processor {self.processor.metadata.name} to be healthy..."
|
578
|
+
f"Waiting for processor {self.processor.metadata.name} to be healthy via health endpoint..."
|
577
579
|
)
|
578
580
|
|
579
581
|
start_time = time.time()
|
580
582
|
while time.time() - start_time < timeout:
|
581
583
|
try:
|
582
|
-
#
|
583
|
-
|
584
|
-
"
|
585
|
-
"id": str(uuid.uuid4()),
|
586
|
-
"content": {},
|
587
|
-
"created_at": time.time(),
|
588
|
-
}
|
589
|
-
|
590
|
-
# Send health check and wait for response
|
591
|
-
response = self.send(
|
592
|
-
data=health_check_data, # type: ignore[arg-type]
|
593
|
-
wait=True,
|
594
|
-
timeout=30.0, # Short timeout for individual health check
|
584
|
+
health_response = self.health() # Use the new health() method
|
585
|
+
logger.info(
|
586
|
+
f">>> Health check response: {health_response.model_dump_json()}"
|
595
587
|
)
|
596
|
-
logger.info(f">>> Health check response: {response}")
|
597
588
|
|
598
589
|
# Check if the response indicates health
|
599
|
-
if
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
logger.info(
|
608
|
-
f"Health check attempt failed, retrying in {retry_interval}s..."
|
609
|
-
)
|
590
|
+
if health_response.status == "ok": # Check for "ok" status
|
591
|
+
logger.info(f"Processor {self.processor.metadata.name} is healthy!")
|
592
|
+
return
|
593
|
+
else:
|
594
|
+
logger.info(
|
595
|
+
f"Processor {self.processor.metadata.name} reported status: {health_response.status}. Retrying in {retry_interval}s..."
|
596
|
+
)
|
610
597
|
|
611
598
|
except Exception as e:
|
612
599
|
logger.info(
|
@@ -619,3 +606,39 @@ class Processor(Generic[InputType, OutputType]):
|
|
619
606
|
raise TimeoutError(
|
620
607
|
f"Processor {self.processor.metadata.name} failed to become healthy within {timeout} seconds"
|
621
608
|
)
|
609
|
+
|
610
|
+
def health(self) -> V1ProcessorHealthResponse:
|
611
|
+
"""
|
612
|
+
Performs a health check on the processor by calling the health endpoint.
|
613
|
+
"""
|
614
|
+
if (
|
615
|
+
not self.processor
|
616
|
+
or not self.processor.metadata.name
|
617
|
+
or not self.processor.metadata.namespace
|
618
|
+
):
|
619
|
+
raise ValueError(
|
620
|
+
"Processor not found or missing metadata (name/namespace), cannot perform health check."
|
621
|
+
)
|
622
|
+
|
623
|
+
health_url = f"{self.orign_host}/v1/processors/{self.processor.metadata.namespace}/{self.processor.metadata.name}/health"
|
624
|
+
logger.debug(f"Calling health check endpoint: {health_url}")
|
625
|
+
|
626
|
+
try:
|
627
|
+
response = requests.get(
|
628
|
+
health_url,
|
629
|
+
headers={"Authorization": f"Bearer {self.api_key}"},
|
630
|
+
timeout=30.0, # Standard timeout for a health check
|
631
|
+
)
|
632
|
+
response.raise_for_status() # Raise an exception for HTTP errors
|
633
|
+
health_response_data = response.json()
|
634
|
+
return V1ProcessorHealthResponse.model_validate(health_response_data)
|
635
|
+
except requests.exceptions.RequestException as e:
|
636
|
+
logger.error(f"Health check request to {health_url} failed: {e}")
|
637
|
+
# Optionally, return a V1ProcessorHealthResponse indicating an error
|
638
|
+
# For now, re-raising the exception or a custom one might be better
|
639
|
+
raise RuntimeError(f"Failed to get health status: {e}") from e
|
640
|
+
except Exception as e:
|
641
|
+
logger.error(f"An unexpected error occurred during health check: {e}")
|
642
|
+
raise RuntimeError(
|
643
|
+
f"Unexpected error during health status retrieval: {e}"
|
644
|
+
) from e
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|