From 64641f8c487ae7b5943075b92a1ea76b0731585a Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Tue, 10 Mar 2026 14:39:07 +0100
Subject: [PATCH 1/9] Add overcommit spec and status
---
api/v1/hypervisor_types.go | 29 +++++++++++-
api/v1/zz_generated.deepcopy.go | 19 +++++++-
applyconfigurations/api/v1/hypervisorspec.go | 45 +++++++++++++------
.../api/v1/hypervisorstatus.go | 28 +++++++++---
.../crd/bases/kvm.cloud.sap_hypervisors.yaml | 32 ++++++++++++-
5 files changed, 129 insertions(+), 24 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index e67bbb0..c35295d 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -18,6 +18,7 @@ limitations under the License.
package v1
import (
+ corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -133,6 +134,14 @@ type HypervisorSpec struct {
// +kubebuilder:optional
// MaintenanceReason provides the reason for manual maintenance mode.
MaintenanceReason string `json:"maintenanceReason,omitempty"`
+
+ // Overcommit specifies the desired overcommit ratio by resource type.
+ //
+ // If no overcommit is specified for a resource type, the default overcommit
+ // ratio of 1 should be applied.
+ //
+ // +kubebuilder:validation:Optional
+ Overcommit map[corev1.ResourceName]uint `json:"overcommit,omitempty"`
}
const (
@@ -337,11 +346,27 @@ type HypervisorStatus struct {
// Auto-discovered resource allocation of all hosted VMs.
// +kubebuilder:validation:Optional
- Allocation map[string]resource.Quantity `json:"allocation"`
+ Allocation map[corev1.ResourceName]resource.Quantity `json:"allocation"`
// Auto-discovered capacity of the hypervisor.
+ //
+ // Note that this capacity does not include the applied overcommit ratios,
+ // and represents the actual capacity of the hypervisor. Use the
+ // effective capacity field to get the capacity considering the applied
+ // overcommit ratios.
+ //
// +kubebuilder:validation:Optional
- Capacity map[string]resource.Quantity `json:"capacity"`
+ Capacity map[corev1.ResourceName]resource.Quantity `json:"capacity"`
+
+ // Auto-discovered capacity of the hypervisor, considering the
+ // applied overcommit ratios.
+ //
+ // In case no overcommit ratio is specified for a resource type, the default
+ // overcommit ratio of 1 should be applied, meaning the effective capacity
+ // is the same as the actual capacity.
+ //
+ // +kubebuilder:validation:Optional
+ EffectiveCapacity map[corev1.ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
// Auto-discovered cells on this hypervisor.
// +kubebuilder:validation:Optional
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index daced0c..7c2f5c7 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -22,6 +22,7 @@ limitations under the License.
package v1
import (
+ corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -311,6 +312,13 @@ func (in *HypervisorSpec) DeepCopyInto(out *HypervisorSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.Overcommit != nil {
+ in, out := &in.Overcommit, &out.Overcommit
+ *out = make(map[corev1.ResourceName]uint, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HypervisorSpec.
@@ -337,14 +345,21 @@ func (in *HypervisorStatus) DeepCopyInto(out *HypervisorStatus) {
in.DomainCapabilities.DeepCopyInto(&out.DomainCapabilities)
if in.Allocation != nil {
in, out := &in.Allocation, &out.Allocation
- *out = make(map[string]resource.Quantity, len(*in))
+ *out = make(map[corev1.ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Capacity != nil {
in, out := &in.Capacity, &out.Capacity
- *out = make(map[string]resource.Quantity, len(*in))
+ *out = make(map[corev1.ResourceName]resource.Quantity, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val.DeepCopy()
+ }
+ }
+ if in.EffectiveCapacity != nil {
+ in, out := &in.EffectiveCapacity, &out.EffectiveCapacity
+ *out = make(map[corev1.ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
diff --git a/applyconfigurations/api/v1/hypervisorspec.go b/applyconfigurations/api/v1/hypervisorspec.go
index d116b21..c8a18d9 100644
--- a/applyconfigurations/api/v1/hypervisorspec.go
+++ b/applyconfigurations/api/v1/hypervisorspec.go
@@ -2,22 +2,27 @@
package v1
+import (
+ corev1 "k8s.io/api/core/v1"
+)
+
// HypervisorSpecApplyConfiguration represents a declarative configuration of the HypervisorSpec type for use
// with apply.
type HypervisorSpecApplyConfiguration struct {
- OperatingSystemVersion *string `json:"version,omitempty"`
- Reboot *bool `json:"reboot,omitempty"`
- EvacuateOnReboot *bool `json:"evacuateOnReboot,omitempty"`
- LifecycleEnabled *bool `json:"lifecycleEnabled,omitempty"`
- SkipTests *bool `json:"skipTests,omitempty"`
- CustomTraits []string `json:"customTraits,omitempty"`
- Aggregates []string `json:"aggregates,omitempty"`
- AllowedProjects []string `json:"allowedProjects,omitempty"`
- HighAvailability *bool `json:"highAvailability,omitempty"`
- CreateCertManagerCertificate *bool `json:"createCertManagerCertificate,omitempty"`
- InstallCertificate *bool `json:"installCertificate,omitempty"`
- Maintenance *string `json:"maintenance,omitempty"`
- MaintenanceReason *string `json:"maintenanceReason,omitempty"`
+ OperatingSystemVersion *string `json:"version,omitempty"`
+ Reboot *bool `json:"reboot,omitempty"`
+ EvacuateOnReboot *bool `json:"evacuateOnReboot,omitempty"`
+ LifecycleEnabled *bool `json:"lifecycleEnabled,omitempty"`
+ SkipTests *bool `json:"skipTests,omitempty"`
+ CustomTraits []string `json:"customTraits,omitempty"`
+ Aggregates []string `json:"aggregates,omitempty"`
+ AllowedProjects []string `json:"allowedProjects,omitempty"`
+ HighAvailability *bool `json:"highAvailability,omitempty"`
+ CreateCertManagerCertificate *bool `json:"createCertManagerCertificate,omitempty"`
+ InstallCertificate *bool `json:"installCertificate,omitempty"`
+ Maintenance *string `json:"maintenance,omitempty"`
+ MaintenanceReason *string `json:"maintenanceReason,omitempty"`
+ Overcommit map[corev1.ResourceName]uint `json:"overcommit,omitempty"`
}
// HypervisorSpecApplyConfiguration constructs a declarative configuration of the HypervisorSpec type for use with
@@ -135,3 +140,17 @@ func (b *HypervisorSpecApplyConfiguration) WithMaintenanceReason(value string) *
b.MaintenanceReason = &value
return b
}
+
+// WithOvercommit puts the entries into the Overcommit field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the Overcommit field,
+// overwriting an existing map entries in Overcommit field with the same key.
+func (b *HypervisorSpecApplyConfiguration) WithOvercommit(entries map[corev1.ResourceName]uint) *HypervisorSpecApplyConfiguration {
+ if b.Overcommit == nil && len(entries) > 0 {
+ b.Overcommit = make(map[corev1.ResourceName]uint, len(entries))
+ }
+ for k, v := range entries {
+ b.Overcommit[k] = v
+ }
+ return b
+}
diff --git a/applyconfigurations/api/v1/hypervisorstatus.go b/applyconfigurations/api/v1/hypervisorstatus.go
index ffa0b23..cf2767a 100644
--- a/applyconfigurations/api/v1/hypervisorstatus.go
+++ b/applyconfigurations/api/v1/hypervisorstatus.go
@@ -3,6 +3,7 @@
package v1
import (
+ corev1 "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
@@ -17,8 +18,9 @@ type HypervisorStatusApplyConfiguration struct {
Instances []InstanceApplyConfiguration `json:"instances,omitempty"`
Capabilities *CapabilitiesApplyConfiguration `json:"capabilities,omitempty"`
DomainCapabilities *DomainCapabilitiesApplyConfiguration `json:"domainCapabilities,omitempty"`
- Allocation map[string]resource.Quantity `json:"allocation,omitempty"`
- Capacity map[string]resource.Quantity `json:"capacity,omitempty"`
+ Allocation map[corev1.ResourceName]resource.Quantity `json:"allocation,omitempty"`
+ Capacity map[corev1.ResourceName]resource.Quantity `json:"capacity,omitempty"`
+ EffectiveCapacity map[corev1.ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
Cells []CellApplyConfiguration `json:"cells,omitempty"`
NumInstances *int `json:"numInstances,omitempty"`
HypervisorID *string `json:"hypervisorId,omitempty"`
@@ -102,9 +104,9 @@ func (b *HypervisorStatusApplyConfiguration) WithDomainCapabilities(value *Domai
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Allocation field,
// overwriting an existing map entries in Allocation field with the same key.
-func (b *HypervisorStatusApplyConfiguration) WithAllocation(entries map[string]resource.Quantity) *HypervisorStatusApplyConfiguration {
+func (b *HypervisorStatusApplyConfiguration) WithAllocation(entries map[corev1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
if b.Allocation == nil && len(entries) > 0 {
- b.Allocation = make(map[string]resource.Quantity, len(entries))
+ b.Allocation = make(map[corev1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.Allocation[k] = v
@@ -116,9 +118,9 @@ func (b *HypervisorStatusApplyConfiguration) WithAllocation(entries map[string]r
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Capacity field,
// overwriting an existing map entries in Capacity field with the same key.
-func (b *HypervisorStatusApplyConfiguration) WithCapacity(entries map[string]resource.Quantity) *HypervisorStatusApplyConfiguration {
+func (b *HypervisorStatusApplyConfiguration) WithCapacity(entries map[corev1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
if b.Capacity == nil && len(entries) > 0 {
- b.Capacity = make(map[string]resource.Quantity, len(entries))
+ b.Capacity = make(map[corev1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.Capacity[k] = v
@@ -126,6 +128,20 @@ func (b *HypervisorStatusApplyConfiguration) WithCapacity(entries map[string]res
return b
}
+// WithEffectiveCapacity puts the entries into the EffectiveCapacity field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the EffectiveCapacity field,
+// overwriting an existing map entries in EffectiveCapacity field with the same key.
+func (b *HypervisorStatusApplyConfiguration) WithEffectiveCapacity(entries map[corev1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
+ if b.EffectiveCapacity == nil && len(entries) > 0 {
+ b.EffectiveCapacity = make(map[corev1.ResourceName]resource.Quantity, len(entries))
+ }
+ for k, v := range entries {
+ b.EffectiveCapacity[k] = v
+ }
+ return b
+}
+
// WithCells adds the given value to the Cells field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Cells field.
diff --git a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
index 779055d..0dac757 100644
--- a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
+++ b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
@@ -165,6 +165,15 @@ spec:
description: MaintenanceReason provides the reason for manual maintenance
mode.
type: string
+ overcommit:
+ additionalProperties:
+ type: integer
+ description: |-
+ Overcommit specifies the desired overcommit ratio by resource type.
+
+ If no overcommit is specified for a resource type, the default overcommit
+ ratio of 1 should be applied.
+ type: object
reboot:
default: false
description: Reboot request an reboot after successful installation
@@ -259,7 +268,13 @@ spec:
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
- description: Auto-discovered capacity of the hypervisor.
+ description: |-
+ Auto-discovered capacity of the hypervisor.
+
+ Note that this capacity does not include the applied overcommit ratios,
+ and represents the actual capacity of the hypervisor. Use the
+ effective capacity field to get the capacity considering the applied
+ overcommit ratios.
type: object
cells:
description: Auto-discovered cells on this hypervisor.
@@ -416,6 +431,21 @@ spec:
type: string
type: array
type: object
+ effectiveCapacity:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Auto-discovered capacity of the hypervisor, considering the
+ applied overcommit ratios.
+
+ In case no overcommit ratio is specified for a resource type, the default
+ overcommit ratio of 1 should be applied, meaning the effective capacity
+ is the same as the actual capacity.
+ type: object
evicted:
description: Evicted indicates whether the hypervisor is evicted.
(no instances left with active maintenance mode)
From 6652086bed5d0a1b829ebd3f5d9ba59e7a28456e Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Wed, 11 Mar 2026 12:49:22 +0100
Subject: [PATCH 2/9] Copy over ResourceName from corev1 so we can extend it
---
api/v1/hypervisor_types.go | 30 +++++++++++++++++--
api/v1/zz_generated.deepcopy.go | 6 ++--
.../api/v1/hypervisorstatus.go | 20 ++++++-------
3 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index c35295d..8b86e12 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -23,6 +23,30 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
+// ResourceName is the name identifying a hypervisor resource.
+// Note: this type is similar to the type defined in the kubernetes core api,
+// but may be extended to support additional resource types in the future.
+// See: https://github.com/kubernetes/api/blob/7e7aaba/core/v1/types.go#L6954-L6970
+type ResourceName string
+
+// Resource names must be not more than 63 characters, consisting of upper- or
+// lower-case alphanumeric characters, with the -, _, and . characters allowed
+// anywhere, except the first or last character. The default convention,
+// matching that for annotations, is to use lower-case names, with dashes,
+// rather than camel case, separating compound words. Fully-qualified resource
+// typenames are constructed from a DNS-style subdomain, followed by a slash `/`
+// and a name.
+const (
+ // CPU, in cores. (500m = .5 cores)
+ ResourceCPU ResourceName = "cpu"
+ // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
+ ResourceMemory ResourceName = "memory"
+ // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
+ ResourceStorage ResourceName = "storage"
+ // Local ephemeral storage, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
+ ResourceEphemeralStorage ResourceName = "ephemeral-storage"
+)
+
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
@@ -346,7 +370,7 @@ type HypervisorStatus struct {
// Auto-discovered resource allocation of all hosted VMs.
// +kubebuilder:validation:Optional
- Allocation map[corev1.ResourceName]resource.Quantity `json:"allocation"`
+ Allocation map[ResourceName]resource.Quantity `json:"allocation"`
// Auto-discovered capacity of the hypervisor.
//
@@ -356,7 +380,7 @@ type HypervisorStatus struct {
// overcommit ratios.
//
// +kubebuilder:validation:Optional
- Capacity map[corev1.ResourceName]resource.Quantity `json:"capacity"`
+ Capacity map[ResourceName]resource.Quantity `json:"capacity"`
// Auto-discovered capacity of the hypervisor, considering the
// applied overcommit ratios.
@@ -366,7 +390,7 @@ type HypervisorStatus struct {
// is the same as the actual capacity.
//
// +kubebuilder:validation:Optional
- EffectiveCapacity map[corev1.ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
+ EffectiveCapacity map[ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
// Auto-discovered cells on this hypervisor.
// +kubebuilder:validation:Optional
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 7c2f5c7..0172da9 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -345,21 +345,21 @@ func (in *HypervisorStatus) DeepCopyInto(out *HypervisorStatus) {
in.DomainCapabilities.DeepCopyInto(&out.DomainCapabilities)
if in.Allocation != nil {
in, out := &in.Allocation, &out.Allocation
- *out = make(map[corev1.ResourceName]resource.Quantity, len(*in))
+ *out = make(map[ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Capacity != nil {
in, out := &in.Capacity, &out.Capacity
- *out = make(map[corev1.ResourceName]resource.Quantity, len(*in))
+ *out = make(map[ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.EffectiveCapacity != nil {
in, out := &in.EffectiveCapacity, &out.EffectiveCapacity
- *out = make(map[corev1.ResourceName]resource.Quantity, len(*in))
+ *out = make(map[ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
diff --git a/applyconfigurations/api/v1/hypervisorstatus.go b/applyconfigurations/api/v1/hypervisorstatus.go
index cf2767a..d9b582e 100644
--- a/applyconfigurations/api/v1/hypervisorstatus.go
+++ b/applyconfigurations/api/v1/hypervisorstatus.go
@@ -3,7 +3,7 @@
package v1
import (
- corev1 "k8s.io/api/core/v1"
+ apiv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
@@ -18,9 +18,9 @@ type HypervisorStatusApplyConfiguration struct {
Instances []InstanceApplyConfiguration `json:"instances,omitempty"`
Capabilities *CapabilitiesApplyConfiguration `json:"capabilities,omitempty"`
DomainCapabilities *DomainCapabilitiesApplyConfiguration `json:"domainCapabilities,omitempty"`
- Allocation map[corev1.ResourceName]resource.Quantity `json:"allocation,omitempty"`
- Capacity map[corev1.ResourceName]resource.Quantity `json:"capacity,omitempty"`
- EffectiveCapacity map[corev1.ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
+ Allocation map[apiv1.ResourceName]resource.Quantity `json:"allocation,omitempty"`
+ Capacity map[apiv1.ResourceName]resource.Quantity `json:"capacity,omitempty"`
+ EffectiveCapacity map[apiv1.ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
Cells []CellApplyConfiguration `json:"cells,omitempty"`
NumInstances *int `json:"numInstances,omitempty"`
HypervisorID *string `json:"hypervisorId,omitempty"`
@@ -104,9 +104,9 @@ func (b *HypervisorStatusApplyConfiguration) WithDomainCapabilities(value *Domai
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Allocation field,
// overwriting an existing map entries in Allocation field with the same key.
-func (b *HypervisorStatusApplyConfiguration) WithAllocation(entries map[corev1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
+func (b *HypervisorStatusApplyConfiguration) WithAllocation(entries map[apiv1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
if b.Allocation == nil && len(entries) > 0 {
- b.Allocation = make(map[corev1.ResourceName]resource.Quantity, len(entries))
+ b.Allocation = make(map[apiv1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.Allocation[k] = v
@@ -118,9 +118,9 @@ func (b *HypervisorStatusApplyConfiguration) WithAllocation(entries map[corev1.R
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Capacity field,
// overwriting an existing map entries in Capacity field with the same key.
-func (b *HypervisorStatusApplyConfiguration) WithCapacity(entries map[corev1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
+func (b *HypervisorStatusApplyConfiguration) WithCapacity(entries map[apiv1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
if b.Capacity == nil && len(entries) > 0 {
- b.Capacity = make(map[corev1.ResourceName]resource.Quantity, len(entries))
+ b.Capacity = make(map[apiv1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.Capacity[k] = v
@@ -132,9 +132,9 @@ func (b *HypervisorStatusApplyConfiguration) WithCapacity(entries map[corev1.Res
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the EffectiveCapacity field,
// overwriting an existing map entries in EffectiveCapacity field with the same key.
-func (b *HypervisorStatusApplyConfiguration) WithEffectiveCapacity(entries map[corev1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
+func (b *HypervisorStatusApplyConfiguration) WithEffectiveCapacity(entries map[apiv1.ResourceName]resource.Quantity) *HypervisorStatusApplyConfiguration {
if b.EffectiveCapacity == nil && len(entries) > 0 {
- b.EffectiveCapacity = make(map[corev1.ResourceName]resource.Quantity, len(entries))
+ b.EffectiveCapacity = make(map[apiv1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.EffectiveCapacity[k] = v
From 25185ae32810167c08a298b036bd18e66d2cc639 Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Wed, 11 Mar 2026 12:55:08 +0100
Subject: [PATCH 3/9] Support fractional overcommit values
---
Makefile | 2 +-
api/v1/hypervisor_types.go | 12 +++++--
api/v1/zz_generated.deepcopy.go | 2 +-
applyconfigurations/api/v1/hypervisorspec.go | 32 +++++++++----------
.../crd/bases/kvm.cloud.sap_hypervisors.yaml | 12 +++++--
5 files changed, 38 insertions(+), 22 deletions(-)
diff --git a/Makefile b/Makefile
index 6c80ed3..f3ad0ef 100644
--- a/Makefile
+++ b/Makefile
@@ -117,7 +117,7 @@ check: FORCE static-check build/cover.html build-all
generate: install-controller-gen
@printf "\e[1;36m>> controller-gen\e[0m\n"
- @controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
+ @controller-gen crd:allowDangerousTypes=true rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
@controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
@controller-gen applyconfiguration paths="./..."
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index 8b86e12..97de0f0 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -162,10 +162,15 @@ type HypervisorSpec struct {
// Overcommit specifies the desired overcommit ratio by resource type.
//
// If no overcommit is specified for a resource type, the default overcommit
- // ratio of 1 should be applied.
+ // ratio of 1.0 should be applied, i.e. the effective capacity is the same
+ // as the actual capacity.
+ //
+ // If the overcommit ratio results in a fractional effective capacity,
+ // the effective capacity is expected to be rounded down. This allows
+ // gradually adjusting the hypervisor capacity.
//
// +kubebuilder:validation:Optional
- Overcommit map[corev1.ResourceName]uint `json:"overcommit,omitempty"`
+ Overcommit map[corev1.ResourceName]float64 `json:"overcommit,omitempty"`
}
const (
@@ -389,6 +394,9 @@ type HypervisorStatus struct {
// overcommit ratio of 1 should be applied, meaning the effective capacity
// is the same as the actual capacity.
//
+ // If the overcommit ratio results in a fractional effective capacity, the
+ // effective capacity is expected to be rounded down.
+ //
// +kubebuilder:validation:Optional
EffectiveCapacity map[ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 0172da9..07bcd0e 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -314,7 +314,7 @@ func (in *HypervisorSpec) DeepCopyInto(out *HypervisorSpec) {
}
if in.Overcommit != nil {
in, out := &in.Overcommit, &out.Overcommit
- *out = make(map[corev1.ResourceName]uint, len(*in))
+ *out = make(map[corev1.ResourceName]float64, len(*in))
for key, val := range *in {
(*out)[key] = val
}
diff --git a/applyconfigurations/api/v1/hypervisorspec.go b/applyconfigurations/api/v1/hypervisorspec.go
index c8a18d9..008bef4 100644
--- a/applyconfigurations/api/v1/hypervisorspec.go
+++ b/applyconfigurations/api/v1/hypervisorspec.go
@@ -9,20 +9,20 @@ import (
// HypervisorSpecApplyConfiguration represents a declarative configuration of the HypervisorSpec type for use
// with apply.
type HypervisorSpecApplyConfiguration struct {
- OperatingSystemVersion *string `json:"version,omitempty"`
- Reboot *bool `json:"reboot,omitempty"`
- EvacuateOnReboot *bool `json:"evacuateOnReboot,omitempty"`
- LifecycleEnabled *bool `json:"lifecycleEnabled,omitempty"`
- SkipTests *bool `json:"skipTests,omitempty"`
- CustomTraits []string `json:"customTraits,omitempty"`
- Aggregates []string `json:"aggregates,omitempty"`
- AllowedProjects []string `json:"allowedProjects,omitempty"`
- HighAvailability *bool `json:"highAvailability,omitempty"`
- CreateCertManagerCertificate *bool `json:"createCertManagerCertificate,omitempty"`
- InstallCertificate *bool `json:"installCertificate,omitempty"`
- Maintenance *string `json:"maintenance,omitempty"`
- MaintenanceReason *string `json:"maintenanceReason,omitempty"`
- Overcommit map[corev1.ResourceName]uint `json:"overcommit,omitempty"`
+ OperatingSystemVersion *string `json:"version,omitempty"`
+ Reboot *bool `json:"reboot,omitempty"`
+ EvacuateOnReboot *bool `json:"evacuateOnReboot,omitempty"`
+ LifecycleEnabled *bool `json:"lifecycleEnabled,omitempty"`
+ SkipTests *bool `json:"skipTests,omitempty"`
+ CustomTraits []string `json:"customTraits,omitempty"`
+ Aggregates []string `json:"aggregates,omitempty"`
+ AllowedProjects []string `json:"allowedProjects,omitempty"`
+ HighAvailability *bool `json:"highAvailability,omitempty"`
+ CreateCertManagerCertificate *bool `json:"createCertManagerCertificate,omitempty"`
+ InstallCertificate *bool `json:"installCertificate,omitempty"`
+ Maintenance *string `json:"maintenance,omitempty"`
+ MaintenanceReason *string `json:"maintenanceReason,omitempty"`
+ Overcommit map[corev1.ResourceName]float64 `json:"overcommit,omitempty"`
}
// HypervisorSpecApplyConfiguration constructs a declarative configuration of the HypervisorSpec type for use with
@@ -145,9 +145,9 @@ func (b *HypervisorSpecApplyConfiguration) WithMaintenanceReason(value string) *
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Overcommit field,
// overwriting an existing map entries in Overcommit field with the same key.
-func (b *HypervisorSpecApplyConfiguration) WithOvercommit(entries map[corev1.ResourceName]uint) *HypervisorSpecApplyConfiguration {
+func (b *HypervisorSpecApplyConfiguration) WithOvercommit(entries map[corev1.ResourceName]float64) *HypervisorSpecApplyConfiguration {
if b.Overcommit == nil && len(entries) > 0 {
- b.Overcommit = make(map[corev1.ResourceName]uint, len(entries))
+ b.Overcommit = make(map[corev1.ResourceName]float64, len(entries))
}
for k, v := range entries {
b.Overcommit[k] = v
diff --git a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
index 0dac757..89592bf 100644
--- a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
+++ b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
@@ -167,12 +167,17 @@ spec:
type: string
overcommit:
additionalProperties:
- type: integer
+ type: number
description: |-
Overcommit specifies the desired overcommit ratio by resource type.
If no overcommit is specified for a resource type, the default overcommit
- ratio of 1 should be applied.
+ ratio of 1.0 should be applied, i.e. the effective capacity is the same
+ as the actual capacity.
+
+ If the overcommit ratio results in a fractional effective capacity,
+ the effective capacity is expected to be rounded down. This allows
+ gradually adjusting the hypervisor capacity.
type: object
reboot:
default: false
@@ -445,6 +450,9 @@ spec:
In case no overcommit ratio is specified for a resource type, the default
overcommit ratio of 1 should be applied, meaning the effective capacity
is the same as the actual capacity.
+
+ If the overcommit ratio results in a fractional effective capacity, the
+ effective capacity is expected to be rounded down.
type: object
evicted:
description: Evicted indicates whether the hypervisor is evicted.
From 2d9271658393416af0da1221cfc9105e78e2b93f Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Fri, 13 Mar 2026 09:01:21 +0100
Subject: [PATCH 4/9] Add x-kubernetes-validations rule for overcommit < 1.0
---
api/v1/hypervisor_types.go | 5 +++++
config/crd/bases/kvm.cloud.sap_hypervisors.yaml | 9 ++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index 97de0f0..b5c427b 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -170,6 +170,11 @@ type HypervisorSpec struct {
// gradually adjusting the hypervisor capacity.
//
// +kubebuilder:validation:Optional
+ //
+ // It is validated that all overcommit ratios are greater than or equal to
+ // 1.0, if specified. For this we don't need extra validating webhooks.
+ // See: https://kubernetes.io/blog/2022/09/23/crd-validation-rules-beta/#crd-transition-rules
+ // +kubebuilder:validation:XValidation:rule="self.all(e, e.value >= 1.0)",message="overcommit ratios must be >= 1.0"
Overcommit map[corev1.ResourceName]float64 `json:"overcommit,omitempty"`
}
diff --git a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
index 89592bf..7560624 100644
--- a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
+++ b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.19.0
+ controller-gen.kubebuilder.io/version: v0.18.0
name: hypervisors.kvm.cloud.sap
spec:
group: kvm.cloud.sap
@@ -178,7 +178,14 @@ spec:
If the overcommit ratio results in a fractional effective capacity,
the effective capacity is expected to be rounded down. This allows
gradually adjusting the hypervisor capacity.
+
+ It is validated that all overcommit ratios are greater than or equal to
+ 1.0, if specified. For this we don't need extra validating webhooks.
+ See: https://kubernetes.io/blog/2022/09/23/crd-validation-rules-beta/#crd-transition-rules
type: object
+ x-kubernetes-validations:
+ - message: overcommit ratios must be >= 1.0
+ rule: self.all(e, e.value >= 1.0)
reboot:
default: false
description: Reboot request an reboot after successful installation
From 8db82dd9f64076fcee4598859f4174627ced1687 Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Fri, 13 Mar 2026 09:05:04 +0100
Subject: [PATCH 5/9] Fix typo in comment, fix not using resourcename
everywhere
---
api/v1/hypervisor_types.go | 5 ++-
api/v1/zz_generated.deepcopy.go | 3 +-
applyconfigurations/api/v1/hypervisorspec.go | 34 ++++++++++----------
3 files changed, 20 insertions(+), 22 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index b5c427b..a2870f2 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -18,7 +18,6 @@ limitations under the License.
package v1
import (
- corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -41,7 +40,7 @@ const (
ResourceCPU ResourceName = "cpu"
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceMemory ResourceName = "memory"
- // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
+ // Volume size, in bytes (e.g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
ResourceStorage ResourceName = "storage"
// Local ephemeral storage, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceEphemeralStorage ResourceName = "ephemeral-storage"
@@ -175,7 +174,7 @@ type HypervisorSpec struct {
// 1.0, if specified. For this we don't need extra validating webhooks.
// See: https://kubernetes.io/blog/2022/09/23/crd-validation-rules-beta/#crd-transition-rules
// +kubebuilder:validation:XValidation:rule="self.all(e, e.value >= 1.0)",message="overcommit ratios must be >= 1.0"
- Overcommit map[corev1.ResourceName]float64 `json:"overcommit,omitempty"`
+ Overcommit map[ResourceName]float64 `json:"overcommit,omitempty"`
}
const (
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 07bcd0e..a823cd9 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -22,7 +22,6 @@ limitations under the License.
package v1
import (
- corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -314,7 +313,7 @@ func (in *HypervisorSpec) DeepCopyInto(out *HypervisorSpec) {
}
if in.Overcommit != nil {
in, out := &in.Overcommit, &out.Overcommit
- *out = make(map[corev1.ResourceName]float64, len(*in))
+ *out = make(map[ResourceName]float64, len(*in))
for key, val := range *in {
(*out)[key] = val
}
diff --git a/applyconfigurations/api/v1/hypervisorspec.go b/applyconfigurations/api/v1/hypervisorspec.go
index 008bef4..4d0930b 100644
--- a/applyconfigurations/api/v1/hypervisorspec.go
+++ b/applyconfigurations/api/v1/hypervisorspec.go
@@ -3,26 +3,26 @@
package v1
import (
- corev1 "k8s.io/api/core/v1"
+ apiv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
)
// HypervisorSpecApplyConfiguration represents a declarative configuration of the HypervisorSpec type for use
// with apply.
type HypervisorSpecApplyConfiguration struct {
- OperatingSystemVersion *string `json:"version,omitempty"`
- Reboot *bool `json:"reboot,omitempty"`
- EvacuateOnReboot *bool `json:"evacuateOnReboot,omitempty"`
- LifecycleEnabled *bool `json:"lifecycleEnabled,omitempty"`
- SkipTests *bool `json:"skipTests,omitempty"`
- CustomTraits []string `json:"customTraits,omitempty"`
- Aggregates []string `json:"aggregates,omitempty"`
- AllowedProjects []string `json:"allowedProjects,omitempty"`
- HighAvailability *bool `json:"highAvailability,omitempty"`
- CreateCertManagerCertificate *bool `json:"createCertManagerCertificate,omitempty"`
- InstallCertificate *bool `json:"installCertificate,omitempty"`
- Maintenance *string `json:"maintenance,omitempty"`
- MaintenanceReason *string `json:"maintenanceReason,omitempty"`
- Overcommit map[corev1.ResourceName]float64 `json:"overcommit,omitempty"`
+ OperatingSystemVersion *string `json:"version,omitempty"`
+ Reboot *bool `json:"reboot,omitempty"`
+ EvacuateOnReboot *bool `json:"evacuateOnReboot,omitempty"`
+ LifecycleEnabled *bool `json:"lifecycleEnabled,omitempty"`
+ SkipTests *bool `json:"skipTests,omitempty"`
+ CustomTraits []string `json:"customTraits,omitempty"`
+ Aggregates []string `json:"aggregates,omitempty"`
+ AllowedProjects []string `json:"allowedProjects,omitempty"`
+ HighAvailability *bool `json:"highAvailability,omitempty"`
+ CreateCertManagerCertificate *bool `json:"createCertManagerCertificate,omitempty"`
+ InstallCertificate *bool `json:"installCertificate,omitempty"`
+ Maintenance *string `json:"maintenance,omitempty"`
+ MaintenanceReason *string `json:"maintenanceReason,omitempty"`
+ Overcommit map[apiv1.ResourceName]float64 `json:"overcommit,omitempty"`
}
// HypervisorSpecApplyConfiguration constructs a declarative configuration of the HypervisorSpec type for use with
@@ -145,9 +145,9 @@ func (b *HypervisorSpecApplyConfiguration) WithMaintenanceReason(value string) *
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Overcommit field,
// overwriting an existing map entries in Overcommit field with the same key.
-func (b *HypervisorSpecApplyConfiguration) WithOvercommit(entries map[corev1.ResourceName]float64) *HypervisorSpecApplyConfiguration {
+func (b *HypervisorSpecApplyConfiguration) WithOvercommit(entries map[apiv1.ResourceName]float64) *HypervisorSpecApplyConfiguration {
if b.Overcommit == nil && len(entries) > 0 {
- b.Overcommit = make(map[corev1.ResourceName]float64, len(entries))
+ b.Overcommit = make(map[apiv1.ResourceName]float64, len(entries))
}
for k, v := range entries {
b.Overcommit[k] = v
From d38e9a219ba2e91d3b96442475b8f0cbd2cb04d1 Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Fri, 13 Mar 2026 09:06:17 +0100
Subject: [PATCH 6/9] Fix not using resourcename in cell struct
---
api/v1/hypervisor_types.go | 4 ++--
api/v1/zz_generated.deepcopy.go | 4 ++--
applyconfigurations/api/v1/cell.go | 15 ++++++++-------
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index a2870f2..29b5235 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -342,11 +342,11 @@ type Cell struct {
// Auto-discovered resource allocation of all hosted VMs in this cell.
// +kubebuilder:validation:Optional
- Allocation map[string]resource.Quantity `json:"allocation"`
+ Allocation map[ResourceName]resource.Quantity `json:"allocation"`
// Auto-discovered capacity of this cell.
// +kubebuilder:validation:Optional
- Capacity map[string]resource.Quantity `json:"capacity"`
+ Capacity map[ResourceName]resource.Quantity `json:"capacity"`
}
// HypervisorStatus defines the observed state of Hypervisor
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index a823cd9..95cd99e 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -64,14 +64,14 @@ func (in *Cell) DeepCopyInto(out *Cell) {
*out = *in
if in.Allocation != nil {
in, out := &in.Allocation, &out.Allocation
- *out = make(map[string]resource.Quantity, len(*in))
+ *out = make(map[ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
}
if in.Capacity != nil {
in, out := &in.Capacity, &out.Capacity
- *out = make(map[string]resource.Quantity, len(*in))
+ *out = make(map[ResourceName]resource.Quantity, len(*in))
for key, val := range *in {
(*out)[key] = val.DeepCopy()
}
diff --git a/applyconfigurations/api/v1/cell.go b/applyconfigurations/api/v1/cell.go
index 6e7fc83..e59db2a 100644
--- a/applyconfigurations/api/v1/cell.go
+++ b/applyconfigurations/api/v1/cell.go
@@ -3,15 +3,16 @@
package v1
import (
+ apiv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
)
// CellApplyConfiguration represents a declarative configuration of the Cell type for use
// with apply.
type CellApplyConfiguration struct {
- CellID *uint64 `json:"cellID,omitempty"`
- Allocation map[string]resource.Quantity `json:"allocation,omitempty"`
- Capacity map[string]resource.Quantity `json:"capacity,omitempty"`
+ CellID *uint64 `json:"cellID,omitempty"`
+ Allocation map[apiv1.ResourceName]resource.Quantity `json:"allocation,omitempty"`
+ Capacity map[apiv1.ResourceName]resource.Quantity `json:"capacity,omitempty"`
}
// CellApplyConfiguration constructs a declarative configuration of the Cell type for use with
@@ -32,9 +33,9 @@ func (b *CellApplyConfiguration) WithCellID(value uint64) *CellApplyConfiguratio
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Allocation field,
// overwriting an existing map entries in Allocation field with the same key.
-func (b *CellApplyConfiguration) WithAllocation(entries map[string]resource.Quantity) *CellApplyConfiguration {
+func (b *CellApplyConfiguration) WithAllocation(entries map[apiv1.ResourceName]resource.Quantity) *CellApplyConfiguration {
if b.Allocation == nil && len(entries) > 0 {
- b.Allocation = make(map[string]resource.Quantity, len(entries))
+ b.Allocation = make(map[apiv1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.Allocation[k] = v
@@ -46,9 +47,9 @@ func (b *CellApplyConfiguration) WithAllocation(entries map[string]resource.Quan
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, the entries provided by each call will be put on the Capacity field,
// overwriting an existing map entries in Capacity field with the same key.
-func (b *CellApplyConfiguration) WithCapacity(entries map[string]resource.Quantity) *CellApplyConfiguration {
+func (b *CellApplyConfiguration) WithCapacity(entries map[apiv1.ResourceName]resource.Quantity) *CellApplyConfiguration {
if b.Capacity == nil && len(entries) > 0 {
- b.Capacity = make(map[string]resource.Quantity, len(entries))
+ b.Capacity = make(map[apiv1.ResourceName]resource.Quantity, len(entries))
}
for k, v := range entries {
b.Capacity[k] = v
From f9ef5f4a6774748cadac43f24fdc3dcf3157f3d1 Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Fri, 13 Mar 2026 09:12:41 +0100
Subject: [PATCH 7/9] Fix CEL expression
---
api/v1/hypervisor_types.go | 2 +-
config/crd/bases/kvm.cloud.sap_hypervisors.yaml | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index 29b5235..7b092ac 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -173,7 +173,7 @@ type HypervisorSpec struct {
// It is validated that all overcommit ratios are greater than or equal to
// 1.0, if specified. For this we don't need extra validating webhooks.
// See: https://kubernetes.io/blog/2022/09/23/crd-validation-rules-beta/#crd-transition-rules
- // +kubebuilder:validation:XValidation:rule="self.all(e, e.value >= 1.0)",message="overcommit ratios must be >= 1.0"
+ // +kubebuilder:validation:XValidation:rule="self.all(k, self[k] >= 1.0)",message="overcommit ratios must be >= 1.0"
Overcommit map[ResourceName]float64 `json:"overcommit,omitempty"`
}
diff --git a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
index 7560624..2680e74 100644
--- a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
+++ b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
- controller-gen.kubebuilder.io/version: v0.18.0
+ controller-gen.kubebuilder.io/version: v0.19.0
name: hypervisors.kvm.cloud.sap
spec:
group: kvm.cloud.sap
@@ -185,7 +185,7 @@ spec:
type: object
x-kubernetes-validations:
- message: overcommit ratios must be >= 1.0
- rule: self.all(e, e.value >= 1.0)
+ rule: self.all(k, self[k] >= 1.0)
reboot:
default: false
description: Reboot request an reboot after successful installation
From 8daf5b1f7e2e3f7fc250b2b38d0e6ffe5665fd4e Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Fri, 13 Mar 2026 10:49:42 +0100
Subject: [PATCH 8/9] Only support cpu and memory resource type for now and
document unsupported frac cpus
---
api/v1/hypervisor_types.go | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index 7b092ac..e8eba74 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -36,14 +36,11 @@ type ResourceName string
// typenames are constructed from a DNS-style subdomain, followed by a slash `/`
// and a name.
const (
- // CPU, in cores. (500m = .5 cores)
+ // CPU, in cores. Note that currently, it is not supported to provide
+ // fractional cpu resources, such as 500m for 0.5 cpu.
ResourceCPU ResourceName = "cpu"
// Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
ResourceMemory ResourceName = "memory"
- // Volume size, in bytes (e.g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024)
- ResourceStorage ResourceName = "storage"
- // Local ephemeral storage, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024)
- ResourceEphemeralStorage ResourceName = "ephemeral-storage"
)
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
From e3699e2ccab99ccb4dd866c1c26a3342efcb9b87 Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Fri, 13 Mar 2026 13:06:21 +0100
Subject: [PATCH 9/9] Add effectiveCapacity to cells as well
---
api/v1/hypervisor_types.go | 18 +++++++++++++
api/v1/zz_generated.deepcopy.go | 7 ++++++
applyconfigurations/api/v1/cell.go | 21 +++++++++++++---
.../crd/bases/kvm.cloud.sap_hypervisors.yaml | 25 ++++++++++++++++++-
4 files changed, 67 insertions(+), 4 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index e8eba74..688d7e1 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -342,8 +342,26 @@ type Cell struct {
Allocation map[ResourceName]resource.Quantity `json:"allocation"`
// Auto-discovered capacity of this cell.
+ //
+ // Note that this capacity does not include the applied overcommit ratios,
+ // and represents the actual capacity of the cell. Use the effective capacity
+ // field to get the capacity considering the applied overcommit ratios.
+ //
// +kubebuilder:validation:Optional
Capacity map[ResourceName]resource.Quantity `json:"capacity"`
+
+ // Auto-discovered capacity of this cell, considering the
+ // applied overcommit ratios.
+ //
+ // In case no overcommit ratio is specified for a resource type, the default
+ // overcommit ratio of 1 should be applied, meaning the effective capacity
+ // is the same as the actual capacity.
+ //
+ // If the overcommit ratio results in a fractional effective capacity, the
+ // effective capacity is expected to be rounded down.
+ //
+ // +kubebuilder:validation:Optional
+ EffectiveCapacity map[ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
}
// HypervisorStatus defines the observed state of Hypervisor
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 95cd99e..3dbdc81 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -76,6 +76,13 @@ func (in *Cell) DeepCopyInto(out *Cell) {
(*out)[key] = val.DeepCopy()
}
}
+ if in.EffectiveCapacity != nil {
+ in, out := &in.EffectiveCapacity, &out.EffectiveCapacity
+ *out = make(map[ResourceName]resource.Quantity, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val.DeepCopy()
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cell.
diff --git a/applyconfigurations/api/v1/cell.go b/applyconfigurations/api/v1/cell.go
index e59db2a..6ef834f 100644
--- a/applyconfigurations/api/v1/cell.go
+++ b/applyconfigurations/api/v1/cell.go
@@ -10,9 +10,10 @@ import (
// CellApplyConfiguration represents a declarative configuration of the Cell type for use
// with apply.
type CellApplyConfiguration struct {
- CellID *uint64 `json:"cellID,omitempty"`
- Allocation map[apiv1.ResourceName]resource.Quantity `json:"allocation,omitempty"`
- Capacity map[apiv1.ResourceName]resource.Quantity `json:"capacity,omitempty"`
+ CellID *uint64 `json:"cellID,omitempty"`
+ Allocation map[apiv1.ResourceName]resource.Quantity `json:"allocation,omitempty"`
+ Capacity map[apiv1.ResourceName]resource.Quantity `json:"capacity,omitempty"`
+ EffectiveCapacity map[apiv1.ResourceName]resource.Quantity `json:"effectiveCapacity,omitempty"`
}
// CellApplyConfiguration constructs a declarative configuration of the Cell type for use with
@@ -56,3 +57,17 @@ func (b *CellApplyConfiguration) WithCapacity(entries map[apiv1.ResourceName]res
}
return b
}
+
+// WithEffectiveCapacity puts the entries into the EffectiveCapacity field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the EffectiveCapacity field,
+// overwriting an existing map entries in EffectiveCapacity field with the same key.
+func (b *CellApplyConfiguration) WithEffectiveCapacity(entries map[apiv1.ResourceName]resource.Quantity) *CellApplyConfiguration {
+ if b.EffectiveCapacity == nil && len(entries) > 0 {
+ b.EffectiveCapacity = make(map[apiv1.ResourceName]resource.Quantity, len(entries))
+ }
+ for k, v := range entries {
+ b.EffectiveCapacity[k] = v
+ }
+ return b
+}
diff --git a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
index 2680e74..b479fc6 100644
--- a/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
+++ b/config/crd/bases/kvm.cloud.sap_hypervisors.yaml
@@ -310,12 +310,35 @@ spec:
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
- description: Auto-discovered capacity of this cell.
+ description: |-
+ Auto-discovered capacity of this cell.
+
+ Note that this capacity does not include the applied overcommit ratios,
+ and represents the actual capacity of the cell. Use the effective capacity
+ field to get the capacity considering the applied overcommit ratios.
type: object
cellID:
description: Cell ID.
format: int64
type: integer
+ effectiveCapacity:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Auto-discovered capacity of this cell, considering the
+ applied overcommit ratios.
+
+ In case no overcommit ratio is specified for a resource type, the default
+ overcommit ratio of 1 should be applied, meaning the effective capacity
+ is the same as the actual capacity.
+
+ If the overcommit ratio results in a fractional effective capacity, the
+ effective capacity is expected to be rounded down.
+ type: object
required:
- cellID
type: object