-
Notifications
You must be signed in to change notification settings - Fork 198
Extend deploymentForVirtualMCPServer with spec-driven replicas, terminationGracePeriodSeconds, and Redis password env var #4215
Description
Description
Extend deploymentForVirtualMCPServer in virtualmcpserver_deployment.go to support horizontal scaling for VirtualMCPServer pods. This task sets Deployment.Spec.Replicas from spec.replicas with nil-passthrough (for HPA compatibility), adds terminationGracePeriodSeconds to the pod spec for graceful shutdown, and injects the Redis password as an environment variable (THV_SESSION_REDIS_PASSWORD) from SessionStorageConfig.PasswordRef using the established SecretKeyRef env var pattern.
Context
Epic THV-0047 introduces horizontal scaling support for the ToolHive operator. This task covers the VirtualMCPServer deployment builder changes needed for that support. Once TASK-003 (#275 — regenerate deepcopy and CRD manifests) has made the new Replicas and SessionStorage fields available in the compiled API types, this task consumes them in the deployment builder to make the vMCP Deployment reflect the desired replica count and inject session storage credentials into pods.
The nil-passthrough pattern for replicas is critical: when spec.replicas is nil, Deployment.Spec.Replicas must be omitted (not set to 1), preserving compatibility with HPAs and manual scaling. Redis password injection follows the existing buildOIDCEnvVars and buildHMACSecretEnvVar patterns already in the same file, which source secrets via ValueFrom.SecretKeyRef.
Dependencies: #275 (TASK-003 — Regenerate deepcopy and CRD manifests)
Blocks: TASK-008 (Unit Tests)
Acceptance Criteria
- When
spec.replicasis set to a non-nil value (e.g., 3),deploymentForVirtualMCPServersetsDeployment.Spec.Replicasto that value - When
spec.replicasis nil,Deployment.Spec.Replicasis nil (not set toint32(1)or any other default), allowing HPAs and external scaling tools to manage replica counts -
terminationGracePeriodSecondsis set on the pod spec (Deployment.Spec.Template.Spec.TerminationGracePeriodSeconds) in all cases, regardless of replica count - When
spec.sessionStorageis non-nil,spec.sessionStorage.provider == "redis", andspec.sessionStorage.passwordRefis non-nil,buildEnvVarsForVmcp(or a dedicated helper) appends acorev1.EnvVarnamedTHV_SESSION_REDIS_PASSWORDwithValueFrom.SecretKeyRefsourced frompasswordRef.name/passwordRef.key - When
spec.sessionStorageis nil,provider != "redis", orpasswordRefis nil, noTHV_SESSION_REDIS_PASSWORDenv var is added (no spurious injection) - The Redis password env var is injected into the
vmcpcontainer in the same manner asVMCP_OIDC_CLIENT_SECRETandVMCP_SESSION_HMAC_SECRET— i.e., viaValueFrom.SecretKeyRef, never as a plaintextValue - Existing deployment update logic in
virtualmcpserver_controller.gocontinues to preserveSpec.Replicason update (i.e., when the spec changes, onlySpec.Template,Labels, andAnnotationsare overwritten — notSpec.Replicas). This is already the case; the task must not regress this behavior. - All existing unit and integration tests for
VirtualMCPServerReconcilercontinue to pass after this change - Code reviewed and approved
Technical Approach
Recommended Implementation
There are three distinct changes within virtualmcpserver_deployment.go:
1. Nil-passthrough replica setting in deploymentForVirtualMCPServer
Replace the current hardcoded replicas := int32(1) / Replicas: &replicas pattern with spec-driven logic:
// Before (current):
replicas := int32(1)
// ...
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
// ...
}
// After:
// spec.replicas == nil means hands-off (HPA/manual scaling)
// spec.replicas != nil means set the exact value from spec
Spec: appsv1.DeploymentSpec{
Replicas: vmcp.Spec.Replicas, // nil-passthrough: omits field when nil
// ...
}Note that VirtualMCPServerSpec.Replicas is a *int32 (added by TASK-002, available after TASK-003). Directly assigning vmcp.Spec.Replicas to Deployment.Spec.Replicas (both are *int32) achieves the nil-passthrough without any conditional logic.
2. Set terminationGracePeriodSeconds on the pod spec
Add TerminationGracePeriodSeconds to corev1.PodSpec inside deploymentForVirtualMCPServer. The value should be defined as a package-level constant or derived from a spec field. Based on RFC THV-0047, a sensible default (e.g., 30 seconds) should be set unconditionally:
// Add a package-level constant for the grace period
const vmcpTerminationGracePeriodSeconds = int64(30)
// In deploymentForVirtualMCPServer, inside corev1.PodSpec:
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: int64Ptr(vmcpTerminationGracePeriodSeconds),
ServiceAccountName: serviceAccountName,
// ...
}Add a helper int64Ptr if one does not already exist in the package, or reuse an existing one (check mcpserver_controller.go for int32Ptr as a pattern reference).
3. Redis password env var injection
Extend buildEnvVarsForVmcp (or add a dedicated buildRedisEnvVars helper following the buildOIDCEnvVars / buildHMACSecretEnvVar pattern) to inject THV_SESSION_REDIS_PASSWORD when the session storage provider is Redis and a password reference is configured:
// In buildEnvVarsForVmcp, after the existing HMAC secret injection:
env = append(env, r.buildRedisPasswordEnvVar(vmcp)...)// New helper — follow buildOIDCEnvVars structure exactly:
func (*VirtualMCPServerReconciler) buildRedisPasswordEnvVar(
vmcp *mcpv1alpha1.VirtualMCPServer,
) []corev1.EnvVar {
if vmcp.Spec.SessionStorage == nil ||
vmcp.Spec.SessionStorage.Provider != "redis" ||
vmcp.Spec.SessionStorage.PasswordRef == nil {
return nil
}
return []corev1.EnvVar{{
Name: "THV_SESSION_REDIS_PASSWORD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: vmcp.Spec.SessionStorage.PasswordRef.Name,
},
Key: vmcp.Spec.SessionStorage.PasswordRef.Key,
},
},
}}
}The existing deploymentNeedsUpdate logic in virtualmcpserver_controller.go (lines ~930–937) already preserves Spec.Replicas on update. This task does not need to change that behavior — it only changes what deploymentForVirtualMCPServer returns on initial creation.
Patterns & Frameworks
- Nil-passthrough for
*int32replicas: The architecture mandates thatDeployment.Spec.Replicas == nilwhenspec.replicasis nil. Direct pointer assignment (Replicas: vmcp.Spec.Replicas) achieves this without branching. - SecretKeyRef env var pattern: Follow
buildOIDCEnvVarsandbuildHMACSecretEnvVarin the same file —corev1.EnvVarwithValueFrom.SecretKeyRefpointing to the referenced secret's name and key. Never use a plaintextValue. - Helper function decomposition: Keep
buildEnvVarsForVmcpas a coordinator that delegates to focused helpers. AddbuildRedisPasswordEnvVaras a new private helper in the same style. - Package-level constants for numeric values: New integer values (grace period seconds) should be declared as typed constants at the top of the file alongside the existing
vmcpDefaultPort,vmcpLivenessInitialDelay, etc.
Code Pointers
cmd/thv-operator/controllers/virtualmcpserver_deployment.golines 112–201 —deploymentForVirtualMCPServer: primary function to modify; replacereplicas := int32(1)/Replicas: &replicaswith nil-passthrough and addTerminationGracePeriodSecondstocorev1.PodSpeccmd/thv-operator/controllers/virtualmcpserver_deployment.golines 256–287 —buildEnvVarsForVmcp: addbuildRedisPasswordEnvVarcall after the HMAC secret linecmd/thv-operator/controllers/virtualmcpserver_deployment.golines 289–329 —buildOIDCEnvVars: reference pattern forSecretKeyRefenv var injection;buildRedisPasswordEnvVarshould follow this structurecmd/thv-operator/controllers/virtualmcpserver_deployment.golines 331–348 —buildHMACSecretEnvVar: second reference pattern; note it uses a constructed secret name rather than a spec-provided one (Redis uses spec-providedPasswordRef)cmd/thv-operator/controllers/virtualmcpserver_controller.golines 926–937 — deployment update logic that preservesSpec.Replicas; must not be modified by this taskcmd/thv-operator/controllers/mcpserver_controller.goline 934 —replicas := int32(1)— parallel pattern in MCPServer (TASK-004 will change this; verify both tasks use consistent nil-passthrough)cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go—VirtualMCPServerSpec: after TASK-003,Replicas *int32andSessionStorage *SessionStorageConfigare available herecmd/thv-operator/api/v1alpha1/mcpserver_types.go—SessionStorageConfigstruct definition (defined once, referenced fromVirtualMCPServerSpec.SessionStorage)
Component Interfaces
The key interface changes in deploymentForVirtualMCPServer:
// deploymentForVirtualMCPServer — key changes (illustrative, not full function)
func (r *VirtualMCPServerReconciler) deploymentForVirtualMCPServer(
ctx context.Context,
vmcp *mcpv1alpha1.VirtualMCPServer,
vmcpConfigChecksum string,
typedWorkloads []workloads.TypedWorkload,
) *appsv1.Deployment {
// REMOVED: replicas := int32(1)
// ...
dep := &appsv1.Deployment{
// ...
Spec: appsv1.DeploymentSpec{
Replicas: vmcp.Spec.Replicas, // nil when spec.replicas unset; pointer value when set
// ...
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: int64Ptr(vmcpTerminationGracePeriodSeconds),
// ...existing fields...
},
},
},
}
// ...
}New helper function signature:
// buildRedisPasswordEnvVar returns the THV_SESSION_REDIS_PASSWORD env var when
// sessionStorage.provider == "redis" and passwordRef is set; returns nil otherwise.
func (*VirtualMCPServerReconciler) buildRedisPasswordEnvVar(
vmcp *mcpv1alpha1.VirtualMCPServer,
) []corev1.EnvVarTesting Strategy
Unit tests for the changes in this task are covered by TASK-008. However, the developer should verify the following during implementation to ensure the changes are correct before TASK-008 formally adds them as test cases:
Unit Tests (to be formally written in TASK-008)
-
deploymentForVirtualMCPServerwithspec.replicas = nilreturns a Deployment withSpec.Replicas == nil -
deploymentForVirtualMCPServerwithspec.replicas = 1returns a Deployment with*Spec.Replicas == 1 -
deploymentForVirtualMCPServerwithspec.replicas = 3returns a Deployment with*Spec.Replicas == 3 -
deploymentForVirtualMCPServeralways returns a Deployment withSpec.Template.Spec.TerminationGracePeriodSecondsset to the expected constant value -
buildRedisPasswordEnvVarwithprovider == "redis"and non-nilpasswordRefreturns onecorev1.EnvVarnamedTHV_SESSION_REDIS_PASSWORDwith correctValueFrom.SecretKeyReffields -
buildRedisPasswordEnvVarwith nilSessionStoragereturns no env vars -
buildRedisPasswordEnvVarwithprovider == "memory"returns no env vars -
buildRedisPasswordEnvVarwithprovider == "redis"and nilpasswordRefreturns no env vars
Integration Tests
- Not in scope for this task; integration tests are covered in TASK-008
Edge Cases
-
spec.replicas = 0— ensureDeployment.Spec.Replicasis set toint32(0)(pointer to zero, not nil), as this is a valid value meaning "scale to zero" - Verify existing tests in the VirtualMCPServer controller suite still pass (particularly any tests that assert
Spec.Replicas == 1, which will need updating in TASK-008 or as part of this task)
Out of Scope
- Reconciler-level validation for VirtualMCPServer session storage (warning status condition when
replicas > 1without Redis) — this is owned by the VirtualMCPServer reconciler and is out of scope for this deployment builder task (see TASK-004 for the MCPServer equivalent; a similar reconciler validation path for VirtualMCPServer may be required in a follow-up) - Writing formal unit tests — covered by TASK-008
- VirtualMCPServer vMCP ConfigMap Redis injection — covered by TASK-007
- Any changes to
mcpserver_controller.goormcpserver_runconfig.go - Redis deployment or lifecycle management
- Application-level session storage implementation inside the vMCP process
References
- Epic THV-0047: Horizontal Scaling - CRD and Operator Changes — https://github.com/stacklok/stacklok-epics/issues/264
- Upstream TASK-003: Regenerate deepcopy and CRD manifests — https://github.com/stacklok/stacklok-epics/issues/275
- RFC THV-0047: Manual Horizontal Scaling for vMCP and Proxy Runner — RFC: Horizontal Scaling for vMCP and Proxy Runner toolhive-rfcs#47
buildOIDCEnvVarsreference pattern:cmd/thv-operator/controllers/virtualmcpserver_deployment.golines 289–329buildHMACSecretEnvVarreference pattern:cmd/thv-operator/controllers/virtualmcpserver_deployment.golines 331–348- Deployment update (replica preservation):
cmd/thv-operator/controllers/virtualmcpserver_controller.golines 926–937