Skip to content

Add implementation for llm artifact deployment in gateway#1093

Merged
Arshardh merged 4 commits intowso2:mainfrom
Arshardh:llm-deployment-gateway
Feb 13, 2026
Merged

Add implementation for llm artifact deployment in gateway#1093
Arshardh merged 4 commits intowso2:mainfrom
Arshardh:llm-deployment-gateway

Conversation

@Arshardh
Copy link
Contributor

@Arshardh Arshardh commented Feb 11, 2026

Summary by CodeRabbit

  • New Features

    • YAML-driven LLM provider and proxy deployments: fetch definitions, apply configurations, and manage create/delete flows.
    • Real-time lifecycle events for LLM provider and proxy deploy/undeploy routed to deployment handlers.
  • Bug Fixes

    • Tests and integration updated to accommodate expanded client initialization surface while preserving existing behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

Walkthrough

Control plane client extended to support LLM provider/proxy lifecycle: added new event types, WebSocket handlers, API utilities to fetch/deploy definitions, and constructor wiring for an LLM deployment service plus lazy-resource and template dependencies.

Changes

Cohort / File(s) Summary
Event Types
gateway/gateway-controller/pkg/controlplane/events.go
Added eight exported event and payload types for LLM provider/proxy deployed/undeployed events.
Client constructor & struct
gateway/gateway-controller/cmd/controller/main.go, gateway/gateway-controller/pkg/controlplane/client.go
Extended NewClient signature to accept lazyResourceStateManager and templateDefinitions; added llmDeploymentService field and wiring during client initialization.
WebSocket handlers & LLM flow
gateway/gateway-controller/pkg/controlplane/client.go
Added LLM-specific WebSocket event routing and handlers for provider/proxy deploy/undeploy that validate IDs, fetch definitions, extract YAML, and invoke the LLM deployment service with logging and guards.
API utilities (fetch & deploy)
gateway/gateway-controller/pkg/utils/api_utils.go
Added FetchLLMProviderDefinition, FetchLLMProxyDefinition, CreateLLMProviderFromYAML, CreateLLMProxyFromYAML to fetch ZIPs and trigger deployments via LLMDeploymentService; file contains duplicated declarations.
Tests (call-site updates)
gateway/gateway-controller/pkg/controlplane/client_integration_test.go, gateway/gateway-controller/pkg/controlplane/controlplane_test.go
Updated NewClient call sites to include additional trailing nil arguments to match the expanded constructor arity; no test logic changes.

Sequence Diagram

sequenceDiagram
    participant WS as "WebSocket Event Source"
    participant Client as "Gateway Client"
    participant APIUtils as "API Utils Service"
    participant LLM as "LLM Deployment Service"
    participant CP as "Control Plane API"

    WS->>Client: LLMProviderDeployed event (providerID, correlationID)
    activate Client

    Client->>APIUtils: FetchLLMProviderDefinition(providerID)
    activate APIUtils
    APIUtils->>CP: GET /llm-providers/{providerID}
    CP-->>APIUtils: ZIP bytes
    APIUtils-->>Client: ZIP bytes
    deactivate APIUtils

    Client->>APIUtils: Extract YAML from ZIP
    activate APIUtils
    APIUtils-->>Client: YAML bytes
    deactivate APIUtils

    Client->>APIUtils: CreateLLMProviderFromYAML(yaml, providerID, correlationID, LLM)
    activate APIUtils
    APIUtils->>LLM: DeployLLMProviderConfiguration(...)
    LLM-->>APIUtils: APIDeploymentResult
    APIUtils-->>Client: APIDeploymentResult
    deactivate APIUtils

    Client->>Client: Log result / emit status
    deactivate Client
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I nibble lines of YAML bright,

ZIPs unfold by soft moonlight,
Events hop in tidy rows,
Deployments bloom where codegrass grows,
Hooray — the gateways take their flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is completely empty, missing all required template sections including Purpose, Goals, Approach, User stories, Documentation, Automation tests, Security checks, Samples, Related PRs, and Test environment. Fill in all required sections of the PR template, especially Purpose (with issue links), Goals, Approach, test details, security verification checklist, and test environment information.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add implementation for llm artifact deployment in gateway' accurately describes the main change in the PR, which adds comprehensive LLM deployment functionality including event types, deployment service integration, and handler implementations.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
gateway/gateway-controller/pkg/utils/api_utils.go (1)

122-234: Extract a common HTTP fetch helper to reduce duplication.

FetchLLMProviderDefinition, FetchLLMProxyDefinition, and the existing FetchAPIDefinition are nearly identical (~50 lines each), differing only in the URL path segment and log labels. Consider extracting a private fetchDefinition(resourceType, id string) helper that all three delegate to.

Additionally, the static analysis tool flags the missing MinVersion in the tls.Config on Lines 136 and 193 (CWE-327). The same gap exists in FetchAPIDefinition (Line 80), so this would ideally be addressed in the shared helper as well.

♻️ Sketch of a shared helper
+// fetchDefinition downloads a definition ZIP from the given resource path.
+func (s *APIUtilsService) fetchDefinition(resourcePath, resourceType, resourceID string) ([]byte, error) {
+	url := s.config.BaseURL + resourcePath
+	s.logger.Info("Fetching "+resourceType+" definition",
+		slog.String("id", resourceID),
+		slog.String("url", url),
+	)
+
+	client := &http.Client{
+		Timeout: s.config.Timeout,
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				MinVersion:         tls.VersionTLS12,
+				InsecureSkipVerify: s.config.InsecureSkipVerify,
+			},
+		},
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request: %w", err)
+	}
+	req.Header.Add("api-key", s.config.Token)
+	req.Header.Add("Accept", "application/zip")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to fetch %s definition: %w", resourceType, err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		bodyBytes, _ := io.ReadAll(resp.Body)
+		return nil, fmt.Errorf("%s request failed with status %d: %s", resourceType, resp.StatusCode, string(bodyBytes))
+	}
+
+	bodyBytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read response body: %w", err)
+	}
+
+	s.logger.Info("Successfully fetched "+resourceType+" definition",
+		slog.String("id", resourceID),
+		slog.Int("size_bytes", len(bodyBytes)),
+	)
+	return bodyBytes, nil
+}
+
 func (s *APIUtilsService) FetchAPIDefinition(apiID string) ([]byte, error) {
-	// ... 50 lines ...
+	return s.fetchDefinition("/apis/"+apiID, "API", apiID)
 }
 
 func (s *APIUtilsService) FetchLLMProviderDefinition(providerID string) ([]byte, error) {
-	// ... 50 lines ...
+	return s.fetchDefinition("/llm-providers/"+providerID, "LLM provider", providerID)
 }
 
 func (s *APIUtilsService) FetchLLMProxyDefinition(proxyID string) ([]byte, error) {
-	// ... 50 lines ...
+	return s.fetchDefinition("/llm-proxies/"+proxyID, "LLM proxy", proxyID)
 }
gateway/gateway-controller/pkg/controlplane/client.go (2)

841-922: Consider extracting a common deploy-from-control-plane helper for LLM provider and proxy handlers.

handleLLMProviderDeployedEvent and handleLLMProxyDeployedEvent follow an identical sequence: parse event → validate ID → fetch zip → extract YAML → check deployment service → deploy from YAML. The only differences are the event/payload types, ID field names, and which Fetch*/Create* methods are called. If a third LLM entity type is added, this pattern would repeat again.

This is not blocking, but extracting the common orchestration logic would reduce the ~80 lines of near-duplicate code per handler.

Also applies to: 978-1059


119-135: NewClient now takes 14 positional parameters — consider an options struct in a future refactor.

The growing parameter list makes call sites harder to read and more error-prone when parameters share the same type. An opts struct or functional options pattern would improve maintainability. Not blocking for this PR.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
gateway/gateway-controller/pkg/utils/api_utils.go (2)

122-234: Extract a common fetchDefinitionZip helper to eliminate duplication across all three Fetch methods.

FetchLLMProviderDefinition, FetchLLMProxyDefinition, and the existing FetchAPIDefinition share identical logic — only the URL path segment and log entity name differ. This is classic copy-paste duplication.

Consider something like:

♻️ Sketch of a shared helper
+// fetchDefinitionZip downloads a zip definition from the given resource path.
+func (s *APIUtilsService) fetchDefinitionZip(resourcePath, entityType, entityID string) ([]byte, error) {
+	url := s.config.BaseURL + resourcePath
+	s.logger.Info("Fetching "+entityType+" definition",
+		slog.String("id", entityID),
+		slog.String("url", url),
+	)
+
+	client := s.newHTTPClient()
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request: %w", err)
+	}
+	req.Header.Add("api-key", s.config.Token)
+	req.Header.Add("Accept", "application/zip")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to fetch %s definition: %w", entityType, err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		bodyBytes, _ := io.ReadAll(resp.Body)
+		return nil, fmt.Errorf("%s request failed with status %d: %s", entityType, resp.StatusCode, string(bodyBytes))
+	}
+
+	bodyBytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read response body: %w", err)
+	}
+
+	s.logger.Info("Successfully fetched "+entityType+" definition",
+		slog.String("id", entityID),
+		slog.Int("size_bytes", len(bodyBytes)),
+	)
+	return bodyBytes, nil
+}

Then each public method becomes a one-liner:

func (s *APIUtilsService) FetchLLMProviderDefinition(providerID string) ([]byte, error) {
	return s.fetchDefinitionZip("/llm-providers/"+providerID, "LLM provider", providerID)
}

132-140: Missing MinVersion in TLS config (flagged by static analysis).

The new tls.Config blocks omit MinVersion, defaulting to TLS 1.0 when acting as a server and TLS 1.2 as a client. While the client default is generally acceptable, explicitly setting MinVersion: tls.VersionTLS12 (or tls.VersionTLS13) is a defense-in-depth best practice and silences the static analysis warning. Note that the same issue exists in the pre-existing FetchAPIDefinition (line 79-81) and NotifyAPIDeployment — this would be a good time to fix all of them together.

🔒 Proposed fix (apply to all tls.Config instances)
 TLSClientConfig: &tls.Config{
 	InsecureSkipVerify: s.config.InsecureSkipVerify,
+	MinVersion:         tls.VersionTLS12,
 },

Also applies to: 189-197

gateway/gateway-controller/pkg/controlplane/client.go (1)

824-1042: Consider extracting common logic from the four LLM event handlers.

The deployed handlers (handleLLMProviderDeployedEvent and handleLLMProxyDeployedEvent) share an identical flow: parse event → extract ID → fetch zip → extract YAML → check service nil → create from YAML. Similarly, the two undeployed handlers share: parse event → extract ID → check service nil → delete.

A helper like handleLLMDeployedEvent(event, entityType, fetchFunc, createFunc) could reduce ~100 lines of near-duplicate code.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@gateway/gateway-controller/cmd/controller/main.go`:
- Line 310: The code references non-existent cfg.GatewayController fields
causing a compile error; update the NewClient call that constructs cpClient to
use the correct Config fields: replace cfg.GatewayController.ControlPlane with
cfg.Controller.ControlPlane, replace &cfg.GatewayController.Router with
&cfg.Router, and replace &cfg.GatewayController.APIKey with &cfg.APIKey so the
call to controlplane.NewClient uses the existing Config struct fields (cpClient,
NewClient).
🧹 Nitpick comments (3)
gateway/gateway-controller/pkg/utils/api_utils.go (2)

122-234: Extract a shared fetch helper to eliminate near-identical code.

FetchLLMProviderDefinition and FetchLLMProxyDefinition are nearly verbatim copies of FetchAPIDefinition — only the URL path segment and log entity name differ. This triplicates ~55 lines of HTTP/TLS/error-handling logic that must be kept in sync.

Consider a private helper like fetchDefinitionZip(resourceType, id string) and have all three public methods delegate to it.

♻️ Sketch of shared helper
+// fetchDefinitionZip downloads a ZIP artifact from the control plane for the given resource path.
+func (s *APIUtilsService) fetchDefinitionZip(resourcePath, resourceType, id string) ([]byte, error) {
+	url := s.config.BaseURL + "/" + resourcePath + "/" + id
+	s.logger.Info("Fetching "+resourceType+" definition", slog.String("id", id), slog.String("url", url))
+
+	client := &http.Client{
+		Timeout: s.config.Timeout,
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				InsecureSkipVerify: s.config.InsecureSkipVerify,
+			},
+		},
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create request: %w", err)
+	}
+	req.Header.Add("api-key", s.config.Token)
+	req.Header.Add("Accept", "application/zip")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, fmt.Errorf("failed to fetch %s definition: %w", resourceType, err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		bodyBytes, _ := io.ReadAll(resp.Body)
+		return nil, fmt.Errorf("%s request failed with status %d: %s", resourceType, resp.StatusCode, string(bodyBytes))
+	}
+
+	bodyBytes, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read response body: %w", err)
+	}
+
+	s.logger.Info("Successfully fetched "+resourceType+" definition", slog.String("id", id), slog.Int("size_bytes", len(bodyBytes)))
+	return bodyBytes, nil
+}
+
 func (s *APIUtilsService) FetchAPIDefinition(apiID string) ([]byte, error) {
-	// ... 55 lines ...
+	return s.fetchDefinitionZip("apis", "API", apiID)
 }
 
 func (s *APIUtilsService) FetchLLMProviderDefinition(providerID string) ([]byte, error) {
-	// ... 55 lines ...
+	return s.fetchDefinitionZip("llm-providers", "LLM provider", providerID)
 }
 
 func (s *APIUtilsService) FetchLLMProxyDefinition(proxyID string) ([]byte, error) {
-	// ... 55 lines ...
+	return s.fetchDefinitionZip("llm-proxies", "LLM proxy", proxyID)
 }

The same applies to CreateLLMProviderFromYAML / CreateLLMProxyFromYAML (lines 291-327), though the duplication there is smaller.


132-140: Missing MinVersion in TLS config (also applies to lines 190-196).

Static analysis flags tls.Config without MinVersion, defaulting to TLS 1.0 when acting as a server / TLS 1.2 as client. While TLS 1.2 client default is acceptable today, explicitly setting MinVersion: tls.VersionTLS12 (or tls.VersionTLS13) is a best-practice hardening step. Note: the pre-existing FetchAPIDefinition has the same gap, so this could be addressed holistically (e.g., in the shared helper suggested above).

gateway/gateway-controller/pkg/controlplane/client.go (1)

841-922: Consider extracting shared deployment logic for LLM provider and proxy handlers.

handleLLMProviderDeployedEvent and handleLLMProxyDeployedEvent share identical control flow: parse event → validate ID → fetch ZIP → extract YAML → nil-check service → create from YAML. They differ only in type names, ID field, and method calls. Similarly, the two undeployment handlers (lines 924-976, 1061-1113) share the same structure.

This is less pressing than the api_utils.go duplication since Go's type system makes generic extraction harder here, but worth noting for future maintenance.

Also applies to: 978-1059

@Arshardh Arshardh force-pushed the llm-deployment-gateway branch from f243e5a to 92f2e8e Compare February 13, 2026 14:53
@Arshardh Arshardh merged commit 083d444 into wso2:main Feb 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants