Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions controller/deploy/operator/api/v1alpha1/jumpstarter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,9 @@ type ProvisionerConfig struct {
Image string `json:"image,omitempty"`

// Replicas for this provisioner controller Deployment.
// Set to 0 to suspend the provisioner (Deployment stays but no pods run).
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Minimum=0
Replicas *int32 `json:"replicas,omitempty"`

// Resources overrides the global exporterSets.resources for this provisioner.
Expand Down Expand Up @@ -299,8 +300,10 @@ type TelemetryConfig struct {
// Multiple replicas provide HA; each exporter connects to exactly one replica
// via a persistent MetricsStream, so Prometheus sum-by queries across replicas
// yield exact totals without double-counting (see JEP-0013 DD-8).
// Set to 0 to suspend the telemetry deployment (Deployment stays but no pods run,
// Service and other resources are preserved).
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Minimum=0
Replicas *int32 `json:"replicas,omitempty"`

// Resource requirements for the telemetry pod.
Expand Down Expand Up @@ -352,10 +355,12 @@ type RoutersConfig struct {
Resources corev1.ResourceRequirements `json:"resources,omitempty"`

// Number of router replicas to run.
// Must be a positive integer. Minimum recommended value is 3 for high availability.
// Set to 0 to suspend all routers (existing Deployments are scaled to 0 pods, Services and
// certificates are preserved so the configuration can be restored without reconfiguration).
// Minimum recommended value is 3 for high availability.
// +kubebuilder:default=3
// +kubebuilder:validation:Minimum=1
Replicas int32 `json:"replicas,omitempty"`
// +kubebuilder:validation:Minimum=0
Replicas *int32 `json:"replicas,omitempty"`

// Custom annotations to add to router pod templates.
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
Expand Down Expand Up @@ -390,13 +395,14 @@ type ControllerConfig struct {
Resources corev1.ResourceRequirements `json:"resources,omitempty"`

// Number of controller replicas to run.
// Currently only 1 replica is supported because the controller uses in-memory
// Set to 0 to suspend the controller (Deployment stays but no pods run, all other resources are preserved).
// Currently only 1 running replica is supported because the controller uses in-memory
// state for gRPC stream coordination (Dial/Listen). Values greater than 1 will
// be clamped to 1 with a warning. See https://github.com/jumpstarter-dev/jumpstarter/issues/1013
// for the tracking issue on HA controller support.
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
Replicas int32 `json:"replicas,omitempty"`
// +kubebuilder:validation:Minimum=0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Replicas *int32 `json:"replicas,omitempty"`

// Custom annotations to add to controller pod templates.
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
Expand Down
10 changes: 10 additions & 0 deletions controller/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1085,11 +1085,13 @@ spec:
default: 1
description: |-
Number of controller replicas to run.
Currently only 1 replica is supported because the controller uses in-memory
Set to 0 to suspend the controller (Deployment stays but no pods run, all other resources are preserved).
Currently only 1 running replica is supported because the controller uses in-memory
state for gRPC stream coordination (Dial/Listen). Values greater than 1 will
be clamped to 1 with a warning. See issue 1013 for HA controller support.
be clamped to 1 with a warning. See https://github.com/jumpstarter-dev/jumpstarter/issues/1013
for the tracking issue on HA controller support.
format: int32
minimum: 1
minimum: 0
type: integer
resources:
description: |-
Expand Down Expand Up @@ -1412,9 +1414,11 @@ spec:
type: string
replicas:
default: 1
description: Replicas for this provisioner controller Deployment.
description: |-
Replicas for this provisioner controller Deployment.
Set to 0 to suspend the provisioner (Deployment stays but no pods run).
format: int32
minimum: 1
minimum: 0
type: integer
resources:
description: Resources overrides the global exporterSets.resources
Expand Down Expand Up @@ -1849,9 +1853,11 @@ spec:
default: 3
description: |-
Number of router replicas to run.
Must be a positive integer. Minimum recommended value is 3 for high availability.
Set to 0 to suspend all routers (existing Deployments are scaled to 0 pods, Services and
certificates are preserved so the configuration can be restored without reconfiguration).
Minimum recommended value is 3 for high availability.
format: int32
minimum: 1
minimum: 0
type: integer
resources:
description: |-
Expand Down Expand Up @@ -2148,8 +2154,10 @@ spec:
Multiple replicas provide HA; each exporter connects to exactly one replica
via a persistent MetricsStream, so Prometheus sum-by queries across replicas
yield exact totals without double-counting (see JEP-0013 DD-8).
Set to 0 to suspend the telemetry deployment (Deployment stays but no pods run,
Service and other resources are preserved).
format: int32
minimum: 1
minimum: 0
type: integer
resources:
description: Resource requirements for the telemetry pod.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ func (r *JumpstarterReconciler) reconcileCertificates(ctx context.Context, js *o
}

// Create router certificates (one per replica)
for i := int32(0); i < js.Spec.Routers.Replicas; i++ {
certReplicas := int32(0)
if js.Spec.Routers.Replicas != nil {
certReplicas = *js.Spec.Routers.Replicas
}
for i := int32(0); i < certReplicas; i++ {
if err := r.reconcileRouterCertificate(ctx, js, issuerRef, i); err != nil {
return fmt.Errorf("failed to reconcile router %d certificate: %w", i, err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)

var _ = Describe("createControllerDeployment metrics bind", func() {
Expand All @@ -39,7 +40,7 @@ var _ = Describe("createControllerDeployment metrics bind", func() {
Controller: operatorv1alpha1.ControllerConfig{
Image: "example.com/controller:test",
ImagePullPolicy: corev1.PullIfNotPresent,
Replicas: 1,
Replicas: ptr.To(int32(1)),
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,37 @@ var _ = Describe("hasEnabledProvisioners", func() {
}
Expect(hasEnabledProvisioners(provs)).To(BeTrue())
})

It("should return false when all provisioners have replicas=0 (suspended)", func() {
provs := []operatorv1alpha1.ProvisionerConfig{
{Name: "qemu.jumpstarter.dev", Replicas: ptr.To(int32(0))},
{Name: "corellium.jumpstarter.dev", Replicas: ptr.To(int32(0))},
}
Expect(hasEnabledProvisioners(provs)).To(BeFalse())
})

It("should return true when at least one provisioner has replicas>0 among suspended ones", func() {
provs := []operatorv1alpha1.ProvisionerConfig{
{Name: "qemu.jumpstarter.dev", Replicas: ptr.To(int32(0))},
{Name: "corellium.jumpstarter.dev", Replicas: ptr.To(int32(1))},
}
Expect(hasEnabledProvisioners(provs)).To(BeTrue())
})

It("should return false when provisioner is enabled but replicas=0", func() {
provs := []operatorv1alpha1.ProvisionerConfig{
{Name: "qemu.jumpstarter.dev", Enabled: ptr.To(true), Replicas: ptr.To(int32(0))},
}
Expect(hasEnabledProvisioners(provs)).To(BeFalse())
})

It("should return false when all provisioners are disabled or suspended", func() {
provs := []operatorv1alpha1.ProvisionerConfig{
{Name: "qemu.jumpstarter.dev", Enabled: ptr.To(false)},
{Name: "corellium.jumpstarter.dev", Replicas: ptr.To(int32(0))},
}
Expect(hasEnabledProvisioners(provs)).To(BeFalse())
})
})

var _ = Describe("createExporterSetServiceAccount", func() {
Expand Down
Loading
Loading