-
Notifications
You must be signed in to change notification settings - Fork 196
wire spec-driven replicas and Redis password for VirtualMCPServer #4367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/thv-0047-crd-type-changes
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -927,14 +927,18 @@ func (r *VirtualMCPServerReconciler) ensureDeployment( | |
| // - Update Spec.Template: Contains container spec, volumes, pod metadata (triggers rollout) | ||
| // - Update Labels: For label selectors and queries | ||
| // - Update Annotations: For metadata and tooling | ||
| // - Preserve Spec.Replicas: Allows HPA/VPA to manage scaling independently | ||
| // - Sync Spec.Replicas when spec.replicas is non-nil (operator authoritative) | ||
| // - Preserve Spec.Replicas when spec.replicas is nil (HPA or external controller manages scaling) | ||
| // - Preserve ResourceVersion, UID: Required for optimistic concurrency control | ||
| // | ||
| // Note: If update conflicts occur due to concurrent modifications, the reconcile | ||
| // loop will retry automatically. Kubernetes' optimistic locking prevents data loss. | ||
| deployment.Spec.Template = newDeployment.Spec.Template | ||
| deployment.Labels = newDeployment.Labels | ||
| deployment.Annotations = ctrlutil.MergeAnnotations(newDeployment.Annotations, deployment.Annotations) | ||
| if newDeployment.Spec.Replicas != nil { | ||
| deployment.Spec.Replicas = newDeployment.Spec.Replicas | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: MCPServer (#4364) adds a |
||
|
|
||
| ctxLogger.Info("Updating Deployment", "Deployment.Namespace", deployment.Namespace, "Deployment.Name", deployment.Name) | ||
| if err := r.Update(ctx, deployment); err != nil { | ||
|
|
@@ -1077,6 +1081,14 @@ func (r *VirtualMCPServerReconciler) deploymentNeedsUpdate( | |
| return true | ||
| } | ||
|
|
||
| // Check if spec.replicas has changed. Only compare when spec.replicas is non-nil; | ||
| // nil means hands-off mode (HPA or external controller manages replicas) and the live count is authoritative. | ||
| if vmcp.Spec.Replicas != nil { | ||
| if deployment.Spec.Replicas == nil || *deployment.Spec.Replicas != *vmcp.Spec.Replicas { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| return false | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,9 @@ const ( | |
| vmcpReadinessTimeout = int32(3) // seconds - shorter timeout for faster detection | ||
| vmcpReadinessFailures = int32(3) // consecutive failures before removing from service | ||
|
|
||
| // Graceful shutdown configuration | ||
| vmcpTerminationGracePeriodSeconds = int64(30) // seconds - allow in-flight requests to complete | ||
|
|
||
| // Default resource requirements for VirtualMCPServer vmcp container | ||
| // These provide sensible defaults that can be overridden via PodTemplateSpec | ||
| vmcpDefaultCPURequest = "100m" | ||
|
|
@@ -117,7 +120,6 @@ func (r *VirtualMCPServerReconciler) deploymentForVirtualMCPServer( | |
| typedWorkloads []workloads.TypedWorkload, | ||
| ) *appsv1.Deployment { | ||
| ls := labelsForVirtualMCPServer(vmcp.Name) | ||
| replicas := int32(1) | ||
|
|
||
| // Build deployment components using helper functions | ||
| args := r.buildContainerArgsForVmcp(vmcp) | ||
|
|
@@ -136,7 +138,7 @@ func (r *VirtualMCPServerReconciler) deploymentForVirtualMCPServer( | |
| Annotations: deploymentAnnotations, | ||
| }, | ||
| Spec: appsv1.DeploymentSpec{ | ||
| Replicas: &replicas, | ||
| Replicas: vmcp.Spec.Replicas, | ||
| Selector: &metav1.LabelSelector{ | ||
| MatchLabels: ls, | ||
| }, | ||
|
|
@@ -146,7 +148,8 @@ func (r *VirtualMCPServerReconciler) deploymentForVirtualMCPServer( | |
| Annotations: deploymentTemplateAnnotations, | ||
| }, | ||
| Spec: corev1.PodSpec{ | ||
| ServiceAccountName: serviceAccountName, | ||
| TerminationGracePeriodSeconds: int64Ptr(vmcpTerminationGracePeriodSeconds), | ||
| ServiceAccountName: serviceAccountName, | ||
yrobla marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Containers: []corev1.Container{{ | ||
| Image: getVmcpImage(), | ||
| ImagePullPolicy: corev1.PullIfNotPresent, | ||
|
|
@@ -280,8 +283,8 @@ func (r *VirtualMCPServerReconciler) buildEnvVarsForVmcp( | |
| // Always mount HMAC secret for session token binding. | ||
| env = append(env, r.buildHMACSecretEnvVar(vmcp)) | ||
|
|
||
| // Note: Other secrets (Redis passwords, service account credentials) may be added here in the future | ||
| // following the same pattern of mounting from Kubernetes Secrets as environment variables. | ||
| // Mount Redis password secret when session storage provider is Redis. | ||
| env = append(env, r.buildRedisPasswordEnvVar(vmcp)...) | ||
|
|
||
yrobla marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return ctrlutil.EnsureRequiredEnvVars(ctx, env) | ||
| } | ||
|
|
@@ -347,6 +350,27 @@ func (*VirtualMCPServerReconciler) buildHMACSecretEnvVar(vmcp *mcpv1alpha1.Virtu | |
| } | ||
| } | ||
|
|
||
| // 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.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, | ||
| }, | ||
| }, | ||
| }} | ||
| } | ||
|
|
||
|
Comment on lines
+353
to
+373
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Does this follow the established pattern for how the proxy runner consumes the Redis password? Want to make sure the env var name and secret mounting approach are consistent. |
||
| // buildOutgoingAuthEnvVars builds environment variables for outgoing auth secrets. | ||
| func (r *VirtualMCPServerReconciler) buildOutgoingAuthEnvVars( | ||
| ctx context.Context, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.