Skip to content

Commit abb0bc7

Browse files
committed
refactor: integrate LoadBalancerSls with dynamic GraphQL deployment
1 parent 60a9fd4 commit abb0bc7

File tree

2 files changed

+106
-20
lines changed

2 files changed

+106
-20
lines changed

src/tetra_rp/core/resources/load_balancer_sls/integration.py

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import logging
1010
from typing import List, Optional
1111

12-
from tetra_rp.core.resources import ServerlessResource
12+
from tetra_rp.core.resources import ServerlessResource, ResourceManager
1313
from .client import LoadBalancerSls
1414

1515
log = logging.getLogger(__name__)
@@ -25,12 +25,12 @@ def create_load_balancer_sls_class(
2525
"""
2626
Create a LoadBalancerSls-enabled class following existing deployment patterns.
2727
28-
This function follows the same synchronous pattern as create_remote_class but uses
29-
LoadBalancerSls for execution. The deployment will happen lazily when methods are called.
28+
This function creates a wrapper that deploys the LoadBalancerSls endpoint dynamically
29+
using GraphQL and uses the deployed endpoint URL for LoadBalancerSls execution.
3030
3131
Args:
3232
cls: The class to be wrapped for LoadBalancerSls execution
33-
resource_config: Configuration object specifying the serverless resource
33+
resource_config: Configuration object specifying the serverless resource (with type="LB")
3434
dependencies: List of pip packages to install
3535
system_dependencies: List of system packages to install
3636
extra: Additional parameters for execution
@@ -43,18 +43,75 @@ def create_load_balancer_sls_class(
4343
# Follow the same deployment pattern as existing system
4444
log.info(f"Creating LoadBalancerSls class for {cls.__name__}")
4545

46-
# For now, use the hardcoded URL as requested
47-
# TODO: Integrate with resource_config to get actual deployed endpoint URL
48-
endpoint_url = "https://9ttr6h4l3f17w3.api.runpod.ai"
46+
# Verify this is a LoadBalancer resource
47+
if getattr(resource_config, 'type', None) != 'LB':
48+
raise ValueError(f"Expected LoadBalancer resource with type='LB', got type='{getattr(resource_config, 'type', None)}'")
4949

50-
# Note: resource_config parameter will be used in future for actual deployment
50+
# Create a deployment wrapper that handles async deployment
51+
class LoadBalancerSlsClassWrapper:
52+
def __init__(self):
53+
self._deployed_endpoint_url = None
54+
self._deployment_lock = None
55+
56+
async def _ensure_deployed(self):
57+
"""Ensure the endpoint is deployed and get the URL."""
58+
if self._deployed_endpoint_url:
59+
return self._deployed_endpoint_url
60+
61+
# Use ResourceManager to handle caching properly
62+
resource_manager = ResourceManager()
63+
deployed_resource = await resource_manager.get_or_deploy_resource(resource_config)
5164

52-
log.info(f"Using LoadBalancerSls endpoint: {endpoint_url}")
65+
# Construct the endpoint URL from the deployed endpoint ID
66+
# Format: https://ENDPOINT_ID.api.runpod.ai
67+
self._deployed_endpoint_url = f"https://{deployed_resource.id}.api.runpod.ai"
68+
69+
log.info(f"LoadBalancerSls endpoint ready: {self._deployed_endpoint_url}")
70+
return self._deployed_endpoint_url
71+
72+
def __call__(self, *args, **kwargs):
73+
"""Create an instance that handles dynamic deployment."""
74+
return LoadBalancerSlsInstanceWrapper(
75+
cls, self, dependencies, system_dependencies, args, kwargs
76+
)
77+
78+
return LoadBalancerSlsClassWrapper()
5379

54-
# Create LoadBalancerSls instance following existing patterns
55-
runtime = LoadBalancerSls(endpoint_url=endpoint_url)
5680

57-
# Return the wrapped class using LoadBalancerSls
58-
return runtime.remote_class(
59-
dependencies=dependencies, system_dependencies=system_dependencies
60-
)(cls)
81+
class LoadBalancerSlsInstanceWrapper:
82+
"""Instance wrapper that handles async deployment and method routing."""
83+
84+
def __init__(self, original_cls, class_wrapper, dependencies, system_dependencies, args, kwargs):
85+
self._original_cls = original_cls
86+
self._class_wrapper = class_wrapper
87+
self._dependencies = dependencies
88+
self._system_dependencies = system_dependencies
89+
self._args = args
90+
self._kwargs = kwargs
91+
self._runtime = None
92+
93+
async def _get_runtime(self):
94+
"""Get or create the LoadBalancerSls runtime with deployed endpoint."""
95+
if not self._runtime:
96+
endpoint_url = await self._class_wrapper._ensure_deployed()
97+
self._runtime = LoadBalancerSls(endpoint_url=endpoint_url)
98+
99+
# Apply the remote_class decorator
100+
self._wrapped_class = self._runtime.remote_class(
101+
dependencies=self._dependencies,
102+
system_dependencies=self._system_dependencies
103+
)(self._original_cls)
104+
105+
# Create the actual instance
106+
self._instance = self._wrapped_class(*self._args, **self._kwargs)
107+
108+
return self._runtime, self._instance
109+
110+
def __getattr__(self, name):
111+
"""Route method calls through the deployed runtime."""
112+
async def async_method_wrapper(*args, **kwargs):
113+
runtime, instance = await self._get_runtime()
114+
method = getattr(instance, name)
115+
return await method(*args, **kwargs)
116+
117+
return async_method_wrapper

src/tetra_rp/core/resources/load_balancer_sls_resource.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import os
99
from pydantic import model_validator
1010
from .serverless import ServerlessResource
11+
from .template import PodTemplate, KeyValuePair
12+
from .serverless import get_env_vars
1113

1214
TETRA_IMAGE_TAG = os.environ.get("TETRA_IMAGE_TAG", "latest")
1315
TETRA_GPU_IMAGE = os.environ.get(
@@ -34,9 +36,11 @@ def set_load_balancer_defaults(cls, data: dict):
3436
data["type"] = "LB"
3537

3638
# Set default image based on instanceIds presence
37-
data["imageName"] = (
38-
TETRA_CPU_IMAGE if data.get("instanceIds") else TETRA_GPU_IMAGE
39-
)
39+
# This ensures imageName is available for template creation
40+
if not data.get("imageName"):
41+
data["imageName"] = (
42+
TETRA_CPU_IMAGE if data.get("instanceIds") else TETRA_GPU_IMAGE
43+
)
4044

4145
return data
4246

@@ -47,7 +51,32 @@ def imageName(self):
4751
TETRA_CPU_IMAGE if getattr(self, "instanceIds", None) else TETRA_GPU_IMAGE
4852
)
4953

50-
@property
54+
@property
5155
def type(self):
5256
"""Always return 'LB' for LoadBalancerSls resources."""
53-
return "LB"
57+
return "LB"
58+
59+
@model_validator(mode="after")
60+
def ensure_template_creation(self):
61+
"""Ensure template is created for LoadBalancerSls deployment."""
62+
# Call the parent class template creation logic
63+
if not self.templateId and not self.template and self.imageName:
64+
self.template = PodTemplate(
65+
name=self.resource_id,
66+
imageName=self.imageName,
67+
env=KeyValuePair.from_dict(self.env or get_env_vars()),
68+
)
69+
elif self.template:
70+
self.template.name = f"{self.resource_id}__{self.template.resource_id}"
71+
if self.imageName:
72+
self.template.imageName = self.imageName
73+
if self.env:
74+
self.template.env = KeyValuePair.from_dict(self.env)
75+
76+
return self
77+
78+
def model_dump(self, **kwargs):
79+
"""Override model_dump to ensure type='LB' is included."""
80+
data = super().model_dump(**kwargs)
81+
data["type"] = "LB" # Ensure type is always included in serialization
82+
return data

0 commit comments

Comments
 (0)