diff --git a/.github/workflows/helm-lint.yaml b/.github/workflows/helm-lint.yaml
index b6654087f..064dfd4e7 100644
--- a/.github/workflows/helm-lint.yaml
+++ b/.github/workflows/helm-lint.yaml
@@ -23,7 +23,7 @@ jobs:
- name: Set up Helm
uses: azure/setup-helm@v5.0.1
- - uses: actions/setup-python@v7.0.0
+ - uses: actions/setup-python@v7.1.0
with:
python-version: "3.13"
check-latest: true
diff --git a/.golangci.yaml b/.golangci.yaml
index c2311c4aa..5bb56ddc1 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -70,6 +70,12 @@ linters:
forbid:
# ioutil package has been deprecated: https://github.com/golang/go/issues/42026
- pattern: ^ioutil\..*$
+ # cortex uses the controller-runtime (logr) logger everywhere; log/slog
+ # is intentionally not used in the service so logging stays unified.
+ # Obtain a logger via sigs.k8s.io/controller-runtime/pkg/log (logf.Log /
+ # logf.FromContext / ctrl.LoggerFrom) instead.
+ - pattern: ^slog\..*$
+ msg: use the controller-runtime logr logger (sigs.k8s.io/controller-runtime/pkg/log), not log/slog
# Using http.DefaultServeMux is discouraged because it's a global variable that some packages silently and magically add handlers to (esp. net/http/pprof).
# Applications wishing to use http.ServeMux should obtain local instances through http.NewServeMux() instead of using the global default instance.
- pattern: ^http\.DefaultServeMux$
@@ -160,6 +166,12 @@ linters:
# Ref: https://go.dev/doc/effective_go#redeclaration
- path: (.+)\.go$
text: declaration of "err" shadows declaration at
+ # FailoverReservations are deprecated in favor of FailoverHostReservations,
+ # but remain in active use during the transition. Ignore the deprecation
+ # warning until the old type is fully removed.
+ - linters:
+ - staticcheck
+ text: "SA1019:.*FailoverReservations will be replaced with FailoverHostReservations"
- linters:
- goconst
path: (.+)_test\.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2fe4f3e7..d11094fef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,71 @@
# Changelog
+## Release PR [#1427](https://github.com/cobaltcore-dev/cortex/pull/1427) — 2026-10-26
+
+### Bumped Versions
+
+| Package | Previous | New |
+|---|---|---|
+| cortex | 0.7.1 | 0.7.2 |
+| cortex-postgres | 0.6.28 | 0.6.29 |
+| cortex-shim | 0.1.33 | 0.1.34 |
+| bundles | 0.0.108 | 0.0.109 |
+| cortex-placement-shim | 0.1.33 | 0.1.34 |
+
+### ⚠️ Deprecation
+
+- **`FailoverReservation` CRD deprecated:** The existing `FailoverReservation` type is now deprecated in favor of the new `FailoverHostReservation` subtype. Migrate any tooling referencing `FailoverReservation` to `FailoverHostReservation`.
+
+### Features
+
+- **Failover Host Reservation CRD subtype** ([#1408](https://github.com/cobaltcore-dev/cortex/pull/1408)) — A new `FailoverHostReservation` reservation type supports reserving entire hosts for failover, enabling gang-scheduling of all VMs onto a designated host during evacuation events. Includes a full lifecycle model (Designated → Active → In Use → Used/Revoked/Failed), new spec (`FailoverHostReservationSpec`) with `sourceHosts` field, new status (`FailoverHostReservationStatus`) with `sourceHost` field, three new condition types (`FailoverHostDesignated`, `FailoverHostActive`, `FailoverHostInUse`), and CRD enum validation and kubebuilder print columns updated.
+
+- **New KVM Overview Perses dashboard** — Added `cortex-compute-kvm-overview.json` with rich visualizations for KVM compute infrastructure.
+
+### Bug Fixes
+
+- **Filter out overloaded hosts** ([#1416](https://github.com/cobaltcore-dev/cortex/pull/1416)) — The capacity filter now correctly excludes hosts with negative free CPU or memory (allocation > effective capacity), which were previously silently passed through.
+
+- **Owner ref for CR reservations** ([#1403](https://github.com/cobaltcore-dev/cortex/pull/1403)) — Fixed setting owner references on committed-resource reservations.
+
+- **Confirm CR reservation allocations immediately** ([#1424](https://github.com/cobaltcore-dev/cortex/pull/1424)) — When a VM is already on the target hypervisor, reservation allocations are now confirmed immediately rather than waiting for a subsequent reconciliation cycle.
+
+- **Consistent error log level for overloaded hosts** ([#1421](https://github.com/cobaltcore-dev/cortex/pull/1421)) — Aligned CPU overload log to `traceLog.Error` to match memory overload log (was inconsistently using `traceLog.Info`).
+
+- **VM CRD added to Tilt** ([#1426](https://github.com/cobaltcore-dev/cortex/pull/1426)) — Added the `cobaltcore.cloud_virtualmachines` CRD to the Tiltfile for local development.
+
+### Chores & Refactoring
+
+- **Logging unification — slog → controller-runtime/logr** ([#1400](https://github.com/cobaltcore-dev/cortex/pull/1400), [#1401](https://github.com/cobaltcore-dev/cortex/pull/1401), [#1404](https://github.com/cobaltcore-dev/cortex/pull/1404), [#1407](https://github.com/cobaltcore-dev/cortex/pull/1407)) — Completely removed `log/slog` from the codebase, unifying all logging under controller-runtime's logr (zap-backed). Translation rules: `slog.Info/Warn` → `log.Info`, `slog.Debug` → `log.V(1).Info`, `slog.Error(msg, "error", err)` → `log.Error(err, msg)`. Key interface changes: `Run` now takes `logr.Logger` instead of `*slog.Logger`; `GetTraceLogArgs()` returns `[]any` instead of `[]slog.Attr`; `APIMonitor.Respond` takes `logr.Logger`. The dead `LOG_LEVEL` env var was removed from the Helm template and a **forbidigo lint rule** banning `slog.*` prevents re-introduction.
+
+- **Logging level reduction** ([#1409](https://github.com/cobaltcore-dev/cortex/pull/1409), [#1410](https://github.com/cobaltcore-dev/cortex/pull/1410), [#1412](https://github.com/cobaltcore-dev/cortex/pull/1412), [#1413](https://github.com/cobaltcore-dev/cortex/pull/1413)) — Reduced verbosity across commitments, failover reservations, knowledge, and scheduling modules by downgrading non-essential log lines.
+
+- **Dashboard reorganization** ([#1399](https://github.com/cobaltcore-dev/cortex/pull/1399)) — Perses dashboards moved to a top-level `dashboards/` directory. The KVM details dashboard was renamed and updated with new color overrides, instant queries, an OS version pie chart, and a "Ready" column in the host table.
+
+### Documentation
+
+- **Fix stale dashboard paths** ([#1422](https://github.com/cobaltcore-dev/cortex/pull/1422)) — Updated three docs files to reference `dashboards/` instead of the old `tools/perses/dashboards/` path; listed all 10 current dashboard filenames.
+
+### Dependency Updates
+
+| Dependency | Old | New | PR |
+|---|---|---|---|
+| `debian:trixie-slim` | `a29215f` | `62bd423` (digest) | [#1398](https://github.com/cobaltcore-dev/cortex/pull/1398) |
+| `vm-operator` | v0.2.9 | v0.2.10 | [#1397](https://github.com/cobaltcore-dev/cortex/pull/1397) |
+| `kube-prometheus-stack` | 92.10.1 | 92.13.1 | [#1397](https://github.com/cobaltcore-dev/cortex/pull/1397), [#1420](https://github.com/cobaltcore-dev/cortex/pull/1420) |
+| `openstack-hypervisor-operator` | v1.2.3 | v1.3.0 | [#1414](https://github.com/cobaltcore-dev/cortex/pull/1414) |
+| `actions/setup-python` | v7.0.0 | v7.1.0 | [#1414](https://github.com/cobaltcore-dev/cortex/pull/1414) |
+| `sapcc/go-bits` | `416f3ad` | `568ad9d` (2 digest bumps) | [#1418](https://github.com/cobaltcore-dev/cortex/pull/1418), [#1419](https://github.com/cobaltcore-dev/cortex/pull/1419) |
+
+### ⚠️ Notes
+
+- **`openstack-hypervisor-operator` v1.3.0** brings parallel VM migrations during drains, safe eviction completion (incoming migrations must settle), hardened offboarding with `AgentPodsEvicted` gating, and node termination detection via `deletionTimestamp`. Review drain behavior in staging before rollout.
+- **slog removal:** Any out-of-tree code importing cortex packages that used `*slog.Logger` in `Run` or `GetTraceLogArgs` signatures must update to `logr.Logger` / `[]any`.
+
+**Contributors:** @PhilippMatthes, @SoWieMarkus, @mblos, @renovate[bot], @cortex-ai-agents[bot], Claude Opus 4.7
+
+**PR:** [#1427](https://github.com/cobaltcore-dev/cortex/pull/1427) | **Commits:** 22 | **Files changed:** 95
+
## Release PR [#1393](https://github.com/cobaltcore-dev/cortex/pull/1393)
### Versions
diff --git a/Tiltfile b/Tiltfile
index 446a06255..f72add3e1 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -89,7 +89,10 @@ local('kubectl wait --namespace cert-manager --for=condition=available deploymen
########### Dependency CRDs
# Make sure the local cluster is running if you are running into startup issues here.
-url = 'https://raw.githubusercontent.com/cobaltcore-dev/openstack-hypervisor-operator/c57c5be250635c639c858395c0501aa8c422f4ea/charts/openstack-hypervisor-operator/crds/kvm.cloud.sap_hypervisors.yaml'
+# Pinned SHAs must match the versions in go.mod.
+url = 'https://raw.githubusercontent.com/cobaltcore-dev/openstack-hypervisor-operator/df757ad112ea027f47c198266cb3a323d52ee2fe/charts/openstack-hypervisor-operator/crds/kvm.cloud.sap_hypervisors.yaml'
+local('curl -L ' + url + ' | kubectl apply -f -')
+url = 'https://raw.githubusercontent.com/cobaltcore-dev/vm-operator/66e5e459efeb01bfc599a1cda5921528ab47be19/dist/files/crds/cobaltcore.cloud_virtualmachines.yaml'
local('curl -L ' + url + ' | kubectl apply -f -')
########### Cortex Manager & CRDs
@@ -322,10 +325,10 @@ helm_resource(
labels=['Monitoring'],
resource_deps=['perses'],
)
-watch_file('./tools/perses/dashboards')
+watch_file('./dashboards')
k8s_yaml(local(' '.join([
'kubectl create configmap cortex-perses-dashboards',
- '--from-file=./tools/perses/dashboards/',
+ '--from-file=./dashboards/',
'--dry-run=client -o yaml |',
'kubectl label --local -f - perses.dev/resource=true --dry-run=client -o yaml',
])))
diff --git a/api/external/cinder/messages.go b/api/external/cinder/messages.go
index 632189629..c3e3059ac 100644
--- a/api/external/cinder/messages.go
+++ b/api/external/cinder/messages.go
@@ -4,8 +4,6 @@
package api
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/api/scheduling"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
)
@@ -46,12 +44,12 @@ func (r ExternalSchedulerRequest) GetHosts() []string {
func (r ExternalSchedulerRequest) GetWeights() map[string]float64 {
return r.Weights
}
-func (r ExternalSchedulerRequest) GetTraceLogArgs() []slog.Attr {
- return []slog.Attr{
- slog.String("greq", r.Context.GlobalRequestID),
- slog.String("req", r.Context.RequestID),
- slog.String("user", r.Context.UserID),
- slog.String("project", r.Context.ProjectID),
+func (r ExternalSchedulerRequest) GetTraceLogArgs() []any {
+ return []any{
+ "greq", r.Context.GlobalRequestID,
+ "req", r.Context.RequestID,
+ "user", r.Context.UserID,
+ "project", r.Context.ProjectID,
}
}
func (r ExternalSchedulerRequest) Filter(includedHosts map[string]float64) lib.FilterWeigherPipelineRequest {
diff --git a/api/external/ironcore/messages.go b/api/external/ironcore/messages.go
index 25c71c0a5..19247a7c9 100644
--- a/api/external/ironcore/messages.go
+++ b/api/external/ironcore/messages.go
@@ -4,8 +4,6 @@
package ironcore
import (
- "log/slog"
-
ironcorev1alpha1 "github.com/cobaltcore-dev/cortex/api/external/ironcore/v1alpha1"
"github.com/cobaltcore-dev/cortex/api/scheduling"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
@@ -33,8 +31,8 @@ func (r MachinePipelineRequest) GetWeights() map[string]float64 {
}
return weights
}
-func (r MachinePipelineRequest) GetTraceLogArgs() []slog.Attr {
- return []slog.Attr{}
+func (r MachinePipelineRequest) GetTraceLogArgs() []any {
+ return []any{}
}
func (r MachinePipelineRequest) Filter(includedHosts map[string]float64) lib.FilterWeigherPipelineRequest {
filteredPools := make([]ironcorev1alpha1.MachinePool, 0, len(includedHosts))
diff --git a/api/external/manila/messages.go b/api/external/manila/messages.go
index ad0aef2f1..84058984f 100644
--- a/api/external/manila/messages.go
+++ b/api/external/manila/messages.go
@@ -4,8 +4,6 @@
package api
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/api/scheduling"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
)
@@ -46,12 +44,12 @@ func (r ExternalSchedulerRequest) GetHosts() []string {
func (r ExternalSchedulerRequest) GetWeights() map[string]float64 {
return r.Weights
}
-func (r ExternalSchedulerRequest) GetTraceLogArgs() []slog.Attr {
- return []slog.Attr{
- slog.String("greq", r.Context.GlobalRequestID),
- slog.String("req", r.Context.RequestID),
- slog.String("user", r.Context.UserID),
- slog.String("project", r.Context.ProjectID),
+func (r ExternalSchedulerRequest) GetTraceLogArgs() []any {
+ return []any{
+ "greq", r.Context.GlobalRequestID,
+ "req", r.Context.RequestID,
+ "user", r.Context.UserID,
+ "project", r.Context.ProjectID,
}
}
func (r ExternalSchedulerRequest) Filter(includedHosts map[string]float64) lib.FilterWeigherPipelineRequest {
diff --git a/api/external/nova/messages.go b/api/external/nova/messages.go
index 7cd877bff..33ed4c66f 100644
--- a/api/external/nova/messages.go
+++ b/api/external/nova/messages.go
@@ -6,7 +6,6 @@ package api
import (
"errors"
"fmt"
- "log/slog"
"strings"
"github.com/cobaltcore-dev/cortex/api/scheduling"
@@ -56,16 +55,16 @@ func (r ExternalSchedulerRequest) GetHosts() []string {
func (r ExternalSchedulerRequest) GetWeights() map[string]float64 {
return r.Weights
}
-func (r ExternalSchedulerRequest) GetTraceLogArgs() []slog.Attr {
+func (r ExternalSchedulerRequest) GetTraceLogArgs() []any {
greq := ""
if r.Context.GlobalRequestID != nil {
greq = *r.Context.GlobalRequestID
}
- return []slog.Attr{
- slog.String("greq", greq),
- slog.String("req", r.Context.RequestID),
- slog.String("user", r.Context.UserID),
- slog.String("project", r.Context.ProjectID),
+ return []any{
+ "greq", greq,
+ "req", r.Context.RequestID,
+ "user", r.Context.UserID,
+ "project", r.Context.ProjectID,
}
}
func (r ExternalSchedulerRequest) Filter(includedHosts map[string]float64) lib.FilterWeigherPipelineRequest {
diff --git a/api/external/pods/messages.go b/api/external/pods/messages.go
index 3d0930ef5..4bd6d467a 100644
--- a/api/external/pods/messages.go
+++ b/api/external/pods/messages.go
@@ -4,8 +4,6 @@
package pods
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/api/scheduling"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
corev1 "k8s.io/api/core/v1"
@@ -35,8 +33,8 @@ func (r PodPipelineRequest) GetWeights() map[string]float64 {
}
return weights
}
-func (r PodPipelineRequest) GetTraceLogArgs() []slog.Attr {
- return []slog.Attr{}
+func (r PodPipelineRequest) GetTraceLogArgs() []any {
+ return []any{}
}
func (r PodPipelineRequest) Filter(includedHosts map[string]float64) lib.FilterWeigherPipelineRequest {
filteredNodes := make([]corev1.Node, 0, len(includedHosts))
diff --git a/api/v1alpha1/reservation_types.go b/api/v1alpha1/reservation_types.go
index f52797654..2b9e25470 100644
--- a/api/v1alpha1/reservation_types.go
+++ b/api/v1alpha1/reservation_types.go
@@ -19,11 +19,45 @@ type ReservationType string
const (
// ReservationTypeCommittedResource is a reservation for committed/reserved capacity.
ReservationTypeCommittedResource ReservationType = "CommittedResourceReservation"
+
// ReservationTypeFailover is a reservation for failover capacity.
+ //
+ // Deprecated: FailoverReservations will be replaced with
+ // FailoverHostReservations. See ReservationTypeFailoverHost.
ReservationTypeFailover ReservationType = "FailoverReservation"
+
// ReservationTypeInFlight is a reservation that blocks capacity for virtual
// machines that are currently being scheduled, to avoid double-booking.
ReservationTypeInFlight ReservationType = "InFlightReservation"
+
+ // ReservationTypeFailoverHost is a reservation that blocks an entire
+ // host for failover purposes, so that another host can gang-evacuate
+ // its workloads to a designated spot. If this type is set, the reservation
+ // will follow this lifecycle:
+ //
+ // 1. Designation. The reservation has been created on a host. There may be
+ // virtual machines running on this host that need live migration away. As
+ // long as not all virtual machines can be migrated away from the host, the
+ // reservation stays in this state. For pipeline: no new virtual machine
+ // onto this host.
+ //
+ // 2. Active. No more virtual machines on this host. On evacuation this
+ // space can be used.
+ //
+ // 3. Failed. The descheduling failed repeatedly or takes too long, the host
+ // is marked for a certain time to not be designated again.
+ //
+ // 4. In Use. Ongoing gang evacuation onto this host, which will be reused
+ // by subsequent failover events until the evacuation is completed.
+ //
+ // 5. Revoked (=Deleted). The reservation is no longer valid because it no
+ // longer matches any failover partition. This may be because the host has
+ // been marked as no longer schedulable or has been otherwise changed.
+ //
+ // 6. Used (=Deleted). The failover event has been completed. The
+ // reservation is no longer valid and the host is transformed back into
+ // a regular host.
+ ReservationTypeFailoverHost ReservationType = "FailoverHostReservation"
)
// Label keys for Reservation metadata.
@@ -39,6 +73,7 @@ const (
ReservationTypeLabelCommittedResource = "committed-resource"
ReservationTypeLabelFailover = "failover"
ReservationTypeLabelInFlight = "in-flight"
+ ReservationTypeLabelFailoverHost = "failover-host"
)
// Annotation keys for Reservation metadata.
@@ -101,7 +136,15 @@ type CommittedResourceReservationSpec struct {
Allocations map[string]CommittedResourceAllocation `json:"allocations,omitempty"`
}
+// MatchesGroup reports whether the reservation targets the given project and resource group.
+func (s *CommittedResourceReservationSpec) MatchesGroup(projectID, resourceGroup string) bool {
+ return s.ProjectID == projectID && s.ResourceGroup == resourceGroup
+}
+
// FailoverReservationSpec defines the spec fields specific to failover reservations.
+//
+// Deprecated: FailoverReservations will be replaced with FailoverHostReservations.
+// See FailoverHostReservationSpec.
type FailoverReservationSpec struct {
// ResourceGroup is the group/category of the resource (e.g., "hana_medium_v2").
// +kubebuilder:validation:Optional
@@ -128,10 +171,20 @@ type InFlightReservationSpec struct {
Intent SchedulingIntent `json:"intent"`
}
+// FailoverHostReservationSpec defines the spec fields specific to failover
+// host reservations. See ReservationTypeFailoverHost.
+type FailoverHostReservationSpec struct {
+ // SourceHosts is a list of hosts inside the same failover partition from
+ // which a failover event towards the target host may originate.
+ // See Spec.TargetHost for the target host of the failover reservation.
+ // +kubebuilder:validation:Optional
+ SourceHosts []string `json:"sourceHosts,omitempty"`
+}
+
// ReservationSpec defines the desired state of Reservation.
type ReservationSpec struct {
// Type of reservation.
- // +kubebuilder:validation:Enum=CommittedResourceReservation;FailoverReservation;InFlightReservation
+ // +kubebuilder:validation:Enum=CommittedResourceReservation;FailoverReservation;InFlightReservation;FailoverHostReservation
// +kubebuilder:validation:Required
Type ReservationType `json:"type"`
@@ -170,12 +223,19 @@ type ReservationSpec struct {
// FailoverReservation contains fields specific to failover reservations.
// Only used when Type is FailoverReservation.
+ //
+ // Deprecated: FailoverReservations will be replaced with FailoverHostReservations.
// +kubebuilder:validation:Optional
FailoverReservation *FailoverReservationSpec `json:"failoverReservation,omitempty"`
// InFlightReservation specifies which kind of virtual machine is expected
// to land on the reserved slot. Set when Type is InFlightReservation.
InFlightReservation *InFlightReservationSpec `json:"inFlightReservation,omitempty"`
+
+ // FailoverHostReservation contains fields specific to failover host reservations.
+ // Only used when Type is FailoverHostReservation.
+ // +kubebuilder:validation:Optional
+ FailoverHostReservation *FailoverHostReservationSpec `json:"failoverHostReservation,omitempty"`
}
const (
@@ -198,6 +258,9 @@ type CommittedResourceReservationStatus struct {
}
// FailoverReservationStatus defines the status fields specific to failover reservations.
+//
+// Deprecated: FailoverReservations will be replaced with FailoverHostReservations.
+// See FailoverHostReservationStatus.
type FailoverReservationStatus struct {
// Allocations maps VM/instance UUIDs to the host they are currently allocated on.
// Key: VM/instance UUID, Value: Host name where the VM is currently running.
@@ -221,6 +284,51 @@ type FailoverReservationStatus struct {
// in-flight reservations.
type InFlightReservationStatus struct{} // No captured state for now.
+// FailoverHostReservationStatus defines the status fields specific to failover
+// host reservations.
+type FailoverHostReservationStatus struct {
+ // SourceHost is the actual host from which the failover event originates.
+ // Only set when the condition ConditionTypeFailoverHostInUse is True.
+ // +kubebuilder:validation:Optional
+ SourceHost string `json:"sourceHost,omitempty"`
+}
+
+// ReservationConditionType represents the type of condition for a reservation.
+type ReservationConditionType = string
+
+const (
+ // ConditionTypeFailoverHostDesignated indicates that the reservation has
+ // been created on a host. There may be virtual machines running on this
+ // host that need live migration away. As long as not all virtual machines
+ // can be migrated away from the host, the reservation stays in this state.
+ // For pipeline: no new virtual machine onto this host.
+ //
+ // status=Unknown: It is unknown if the failover host has been designated.
+ // status=False: The failover host has not been designated (there was an error).
+ // status=True: The failover host has been designated successfully.
+ ConditionTypeFailoverHostDesignated ReservationConditionType = "FailoverHostDesignated"
+
+ // ConditionTypeFailoverHostActive indicates that the failover host is
+ // active and ready to accept evacuations. No more virtual machines have
+ // been seen on this host, so it is empty and ready to receive all vms
+ // from another host.
+ //
+ // status=Unknown: The failover host is not yet ready to be used.
+ // status=False: There has been some error or issue preventing the failover
+ // host from being active.
+ // status=True: The failover host is active and ready to accept evacuations.
+ ConditionTypeFailoverHostActive ReservationConditionType = "FailoverHostActive"
+
+ // ConditionTypeFailoverHostInUse indicates that the failover host reservation
+ // is currently being used by an ongoing evacuation event with inbound
+ // virtual machines being evacuated.
+ //
+ // status=Unknown: It is unknown if the failover host is currently in use.
+ // status=False: The failover host is not currently in use.
+ // status=True: The failover host is currently in use.
+ ConditionTypeFailoverHostInUse ReservationConditionType = "FailoverHostInUse"
+)
+
// ReservationStatus defines the observed state of Reservation.
type ReservationStatus struct {
// The current status conditions of the reservation.
@@ -249,6 +357,8 @@ type ReservationStatus struct {
// FailoverReservation contains status fields specific to failover reservations.
// Only used when Type is FailoverReservation.
+ //
+ // Deprecated: FailoverReservations will be replaced with FailoverHostReservations.
// +kubebuilder:validation:Optional
FailoverReservation *FailoverReservationStatus `json:"failoverReservation,omitempty"`
@@ -256,6 +366,11 @@ type ReservationStatus struct {
// Only used when Type is InFlightReservation.
// +kubebuilder:validation:Optional
InFlightReservation *InFlightReservationStatus `json:"inFlightReservation,omitempty"`
+
+ // FailoverHostReservation contains status fields specific to failover
+ // host reservations. Only used when Type is FailoverHostReservation.
+ // +kubebuilder:validation:Optional
+ FailoverHostReservation *FailoverHostReservationStatus `json:"failoverHostReservation,omitempty"`
}
// +kubebuilder:object:root=true
@@ -264,12 +379,12 @@ type ReservationStatus struct {
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".metadata.labels['reservations\\.cortex\\.cloud/type']"
// +kubebuilder:printcolumn:name="Host",type="string",JSONPath=".status.host"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
-// +kubebuilder:printcolumn:name="ResourceGroup",type="string",JSONPath=".spec.committedResourceReservation.resourceGroup"
+// +kubebuilder:printcolumn:name="ResourceGroup",type="string",JSONPath=".spec.committedResourceReservation.resourceGroup",priority=1
// +kubebuilder:printcolumn:name="HA ResourceGroup",type="string",JSONPath=".spec.failoverReservation.resourceGroup",priority=1
-// +kubebuilder:printcolumn:name="Project",type="string",JSONPath=".spec.committedResourceReservation.projectID"
-// +kubebuilder:printcolumn:name="AZ",type="string",JSONPath=".spec.availabilityZone"
+// +kubebuilder:printcolumn:name="Project",type="string",JSONPath=".spec.committedResourceReservation.projectID",priority=1
+// +kubebuilder:printcolumn:name="AZ",type="string",JSONPath=".spec.availabilityZone",priority=1
// +kubebuilder:printcolumn:name="StartTime",type="string",JSONPath=".spec.startTime",priority=1
-// +kubebuilder:printcolumn:name="EndTime",type="string",JSONPath=".spec.endTime"
+// +kubebuilder:printcolumn:name="EndTime",type="string",JSONPath=".spec.endTime",priority=1
// +kubebuilder:printcolumn:name="Resources",type="string",JSONPath=".spec.resources",priority=1
// +kubebuilder:printcolumn:name="LastChanged",type="date",JSONPath=".status.failoverReservation.lastChanged",priority=1
// +kubebuilder:printcolumn:name="AcknowledgedAt",type="date",JSONPath=".status.failoverReservation.acknowledgedAt",priority=1
@@ -302,11 +417,6 @@ type ReservationList struct {
Items []Reservation `json:"items"`
}
-// MatchesGroup reports whether the reservation targets the given project and resource group.
-func (s *CommittedResourceReservationSpec) MatchesGroup(projectID, resourceGroup string) bool {
- return s.ProjectID == projectID && s.ResourceGroup == resourceGroup
-}
-
// IsReady returns true if the reservation has the Ready condition set to True.
func (r *Reservation) IsReady() bool {
return meta.IsStatusConditionTrue(r.Status.Conditions, ReservationConditionReady)
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 01e9ec113..c4f4115ff 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -663,6 +663,41 @@ func (in *DetectorSpec) DeepCopy() *DetectorSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FailoverHostReservationSpec) DeepCopyInto(out *FailoverHostReservationSpec) {
+ *out = *in
+ if in.SourceHosts != nil {
+ in, out := &in.SourceHosts, &out.SourceHosts
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailoverHostReservationSpec.
+func (in *FailoverHostReservationSpec) DeepCopy() *FailoverHostReservationSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(FailoverHostReservationSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FailoverHostReservationStatus) DeepCopyInto(out *FailoverHostReservationStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailoverHostReservationStatus.
+func (in *FailoverHostReservationStatus) DeepCopy() *FailoverHostReservationStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(FailoverHostReservationStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FailoverReservationSpec) DeepCopyInto(out *FailoverReservationSpec) {
*out = *in
@@ -1724,6 +1759,11 @@ func (in *ReservationSpec) DeepCopyInto(out *ReservationSpec) {
*out = new(InFlightReservationSpec)
**out = **in
}
+ if in.FailoverHostReservation != nil {
+ in, out := &in.FailoverHostReservation, &out.FailoverHostReservation
+ *out = new(FailoverHostReservationSpec)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReservationSpec.
@@ -1761,6 +1801,11 @@ func (in *ReservationStatus) DeepCopyInto(out *ReservationStatus) {
*out = new(InFlightReservationStatus)
**out = **in
}
+ if in.FailoverHostReservation != nil {
+ in, out := &in.FailoverHostReservation, &out.FailoverHostReservation
+ *out = new(FailoverHostReservationStatus)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReservationStatus.
diff --git a/cmd/manager/main.go b/cmd/manager/main.go
index 07e69f1c0..9dcd8f85d 100644
--- a/cmd/manager/main.go
+++ b/cmd/manager/main.go
@@ -8,13 +8,10 @@ import (
"crypto/tls"
"encoding/json"
"flag"
- "fmt"
- "log/slog"
"net/http"
"os"
"path/filepath"
"slices"
- "strings"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
@@ -142,7 +139,7 @@ func main() {
novaChecksConfig := conf.GetConfigOrDie[nova.ChecksConfig]()
if len(os.Args) >= 3 {
if err := json.Unmarshal([]byte(os.Args[2]), &novaChecksConfig); err != nil {
- slog.Error("invalid json override for e2e-nova", "err", err)
+ setupLog.Error(err, "invalid json override for e2e-nova")
os.Exit(1)
}
}
@@ -159,14 +156,14 @@ func main() {
commitmentsChecksConfig := conf.GetConfigOrDie[commitments.E2EChecksConfig]()
if len(os.Args) >= 3 {
if err := json.Unmarshal([]byte(os.Args[2]), &commitmentsChecksConfig); err != nil {
- slog.Error("invalid json override for e2e-commitments", "err", err)
+ setupLog.Error(err, "invalid json override for e2e-commitments")
os.Exit(1)
}
}
func() {
defer func() {
if r := recover(); r != nil {
- slog.Error("e2e check failed", "reason", r)
+ setupLog.Error(nil, "e2e check failed", "reason", r)
os.Exit(1)
}
}()
@@ -215,35 +212,6 @@ func main() {
})),
))
- // Configure slog (used across internal packages) with JSON output and
- // level control via the LOG_LEVEL environment variable.
- // Supported values: debug, info (default), warn, error.
- slogLevel := new(slog.LevelVar)
- slogLevel.Set(slog.LevelInfo)
- if lvl := os.Getenv("LOG_LEVEL"); lvl != "" {
- switch strings.ToLower(lvl) {
- case "debug":
- slogLevel.Set(slog.LevelDebug)
- case "info":
- slogLevel.Set(slog.LevelInfo)
- case "warn", "warning":
- slogLevel.Set(slog.LevelWarn)
- case "error":
- slogLevel.Set(slog.LevelError)
- default:
- slogLevel.Set(slog.LevelInfo)
- setupLog.Error(fmt.Errorf("unknown LOG_LEVEL %q, defaulting to info", lvl), "invalid log level",
- "supported", []string{"debug", "info", "warn", "warning", "error"})
- }
- }
- slog.SetDefault(slog.New(monitoring.NewMetricsSlogHandler(
- &logMetricsMonitor,
- slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
- Level: slogLevel,
- }),
- )))
- slog.Info("slog configured", "level", slogLevel.Level().String())
-
// Log the main configuration
setupLog.Info("loaded main configuration",
"enabledControllers", mainConfig.EnabledControllers,
diff --git a/cortex.secrets.example.yaml b/cortex.secrets.example.yaml
index 76d7f2d55..65bc7f50d 100644
--- a/cortex.secrets.example.yaml
+++ b/cortex.secrets.example.yaml
@@ -22,7 +22,7 @@ sharedSSOCert: &sharedSSOCert
selfSigned: "false"
# Logging configuration for local development.
-# Set logLevel to "debug" for verbose output from both zap and slog loggers.
+# Set logLevel to "debug" for verbose output from the controller-runtime (zap) logger.
# Set zapDevel to true for human-readable console logs instead of JSON.
# These apply per sub-chart, for example:
#
diff --git a/tools/perses/dashboards/cortex-compute-kvm-overview.json b/dashboards/cortex-compute-kvm-details.json
similarity index 79%
rename from tools/perses/dashboards/cortex-compute-kvm-overview.json
rename to dashboards/cortex-compute-kvm-details.json
index 28d109f1e..7cfe620af 100644
--- a/tools/perses/dashboards/cortex-compute-kvm-overview.json
+++ b/dashboards/cortex-compute-kvm-details.json
@@ -1,10 +1,10 @@
{
"kind": "Dashboard",
"metadata": {
- "name": "cortex_compute_kvm_-_overview",
- "createdAt": "2026-08-07T12:22:15.443047024Z",
- "updatedAt": "2026-10-12T10:15:35.00421019Z",
- "version": 6,
+ "name": "cortex_compute_kvm_-details",
+ "createdAt": "2026-10-16T08:06:13.058891138Z",
+ "updatedAt": "2026-10-16T08:19:06.956177132Z",
+ "version": 1,
"tags": [
"compute",
"infrastructure",
@@ -14,7 +14,7 @@
},
"spec": {
"display": {
- "name": "Cortex Compute KVM - Overview"
+ "name": "Cortex Compute KVM - Details"
},
"panels": {
"1c635e5426614893a17cdb63fcf68abd": {
@@ -38,19 +38,19 @@
{
"areaOpacity": 1,
"colorMode": "fixed-single",
- "colorValue": "#EE6C6C",
+ "colorValue": "#ff0000",
"queryIndex": 0
},
{
"areaOpacity": 1,
"colorMode": "fixed-single",
- "colorValue": "#fa00ff",
+ "colorValue": "#EE6C6C",
"queryIndex": 1
},
{
"areaOpacity": 1,
"colorMode": "fixed-single",
- "colorValue": "#004cff",
+ "colorValue": "#FFCC00",
"queryIndex": 2
},
{
@@ -87,7 +87,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Workload"
}
}
@@ -99,8 +99,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
- "seriesNameFormat": "Failover"
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Reserved"
}
}
}
@@ -111,8 +111,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
- "seriesNameFormat": "Reserved"
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Failover"
}
}
}
@@ -123,7 +123,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Available"
}
}
@@ -152,7 +152,27 @@
"isStacked": true,
"mode": "value",
"orientation": "vertical",
- "sort": "desc"
+ "sort": "desc",
+ "visual": {
+ "colorOverrides": [
+ {
+ "color": "#59CC8D",
+ "regex": "Available"
+ },
+ {
+ "color": "#ff0000",
+ "regex": "Workload"
+ },
+ {
+ "color": "#FFCC00",
+ "regex": "Failover"
+ },
+ {
+ "color": "#EE6C6C",
+ "regex": "Reserved"
+ }
+ ]
+ }
}
},
"queries": [
@@ -162,7 +182,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
"seriesNameFormat": "Workload"
}
}
@@ -174,8 +195,9 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
- "seriesNameFormat": "Failover"
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Reserved"
}
}
}
@@ -186,8 +208,9 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
- "seriesNameFormat": "Reserved"
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Failover"
}
}
}
@@ -198,7 +221,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
"seriesNameFormat": "Available"
}
}
@@ -233,7 +257,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Total"
}
}
@@ -276,7 +301,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Available (%)"
}
}
@@ -322,6 +348,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
+ "instant": true,
"query": "max by (az, flavor_group, flavor_name) (cortex_committed_resource_capacity_vm_slots_placeable{az=~\"$availability_zone\"})"
}
}
@@ -351,7 +378,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(cortex_kvm_project_instances{availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\"})",
+ "instant": true,
+ "query": "sum(cortex_kvm_project_instances{availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\"})",
"seriesNameFormat": "Total VMs"
}
}
@@ -381,19 +409,19 @@
{
"areaOpacity": 1,
"colorMode": "fixed-single",
- "colorValue": "#EE6C6C",
+ "colorValue": "#ff0000",
"queryIndex": 0
},
{
"areaOpacity": 1,
"colorMode": "fixed-single",
- "colorValue": "#fa00ff",
+ "colorValue": "#EE6C6C",
"queryIndex": 1
},
{
"areaOpacity": 1,
"colorMode": "fixed-single",
- "colorValue": "#004cff",
+ "colorValue": "#FFCC00",
"queryIndex": 2
},
{
@@ -429,7 +457,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Workload"
}
}
@@ -441,8 +469,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
- "seriesNameFormat": "Failover"
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Reserved"
}
}
}
@@ -453,8 +481,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
- "seriesNameFormat": "Reserved"
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Failover"
}
}
}
@@ -465,7 +493,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Available"
}
}
@@ -499,7 +527,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Total"
}
}
@@ -535,7 +564,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "count(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "query": "count(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Number of Hosts"
}
}
@@ -544,117 +573,39 @@
]
}
},
- "9a25fb3bcf30475b8b118e4d54abeeb2": {
- "kind": "Panel",
- "spec": {
- "display": {},
- "plugin": {
- "kind": "Markdown",
- "spec": {
- "text": "
CPU
"
- }
- }
- }
- },
- "b79f75c11fdc400eb90fdc153abd72ed": {
+ "8b6cd566a08141bd9c83d9e336752ad9": {
"kind": "Panel",
"spec": {
"display": {
- "name": "CPU (Including Overcommit) per Node"
+ "name": "Number of VMs (some filters can not be applied atm)"
},
"plugin": {
- "kind": "BarChart",
+ "kind": "TimeSeriesChart",
"spec": {
- "calculation": "last",
- "format": {
- "shortValues": true,
- "unit": "decimal"
+ "legend": {
+ "mode": "table",
+ "position": "right",
+ "values": [
+ "last-number"
+ ]
},
- "groupBy": [
- "compute_host"
- ],
- "isStacked": true,
- "mode": "value",
- "orientation": "vertical",
- "sort": "desc"
- }
- },
- "queries": [
- {
- "kind": "TimeSeriesQuery",
- "spec": {
- "plugin": {
- "kind": "PrometheusTimeSeriesQuery",
- "spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
- "seriesNameFormat": "Workload"
- }
- }
- }
- },
- {
- "kind": "TimeSeriesQuery",
- "spec": {
- "plugin": {
- "kind": "PrometheusTimeSeriesQuery",
- "spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
- "seriesNameFormat": "Failover"
- }
- }
- }
- },
- {
- "kind": "TimeSeriesQuery",
- "spec": {
- "plugin": {
- "kind": "PrometheusTimeSeriesQuery",
- "spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
- "seriesNameFormat": "Reserved"
- }
- }
- }
- },
- {
- "kind": "TimeSeriesQuery",
- "spec": {
- "plugin": {
- "kind": "PrometheusTimeSeriesQuery",
- "spec": {
- "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
- "seriesNameFormat": "Available"
- }
- }
- }
- }
- ]
- }
- },
- "c61f619c77104964aa28667b95cbefb1": {
- "kind": "Panel",
- "spec": {
- "display": {},
- "plugin": {
- "kind": "StatChart",
- "spec": {
- "calculation": "last-number",
- "colorMode": "background_solid",
- "format": {
- "unit": "percent-decimal"
+ "visual": {
+ "areaOpacity": 0.3,
+ "connectNulls": false,
+ "display": "line",
+ "lineStyle": "solid",
+ "lineWidth": 1.25,
+ "pointRadius": 2.75,
+ "stack": "all"
},
- "legendMode": "on",
- "thresholds": {
- "defaultColor": "#EE6C6C",
- "steps": [
- {
- "value": 0.2
- },
- {
- "color": "#59CC8D",
- "value": 0.3
- }
- ]
+ "yAxis": {
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
+ "label": "",
+ "min": 0,
+ "show": true
}
}
},
@@ -665,8 +616,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
- "seriesNameFormat": "Available (%)"
+ "query": "sum by(domain_name, domain_id)(max by (compute_host, domain_id, domain_name, project_id) (cortex_kvm_project_instances{availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\"}))",
+ "seriesNameFormat": "{{domain_name}}"
}
}
}
@@ -674,39 +625,29 @@
]
}
},
- "ccf9476bdfc24a18a976b7f429dd69b4": {
+ "8cde0b45349d4b019aca4cea79627980": {
"kind": "Panel",
"spec": {
"display": {
- "name": "Number of VMs (some filters can not be applied atm)"
+ "name": "OS Version"
},
"plugin": {
- "kind": "TimeSeriesChart",
+ "kind": "PieChart",
"spec": {
+ "calculation": "last",
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
"legend": {
- "mode": "table",
- "position": "right",
- "values": [
- "last-number"
- ]
+ "position": "right"
},
+ "mode": "value",
+ "showLabels": false,
+ "sort": "desc",
"visual": {
- "areaOpacity": 0.3,
- "connectNulls": false,
- "display": "line",
- "lineStyle": "solid",
- "lineWidth": 1.25,
- "pointRadius": 2.75,
- "stack": "all"
- },
- "yAxis": {
- "format": {
- "shortValues": true,
- "unit": "decimal"
- },
- "label": "",
- "min": 0,
- "show": true
+ "innerRadius": 51,
+ "outerRadius": 100
}
}
},
@@ -717,8 +658,9 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum by(domain_name, domain_id)(max by (compute_host, domain_id, domain_name, project_id) (cortex_kvm_project_instances{availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\"}))",
- "seriesNameFormat": "{{domain_name}}"
+ "instant": true,
+ "query": "count(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host, os_version)) by (os_version)",
+ "seriesNameFormat": "{{os_version}}"
}
}
}
@@ -726,51 +668,58 @@
]
}
},
- "ec913f5da9a24a468f0f147d112de6bb": {
+ "9a25fb3bcf30475b8b118e4d54abeeb2": {
"kind": "Panel",
"spec": {
"display": {},
"plugin": {
"kind": "Markdown",
"spec": {
- "text": "RAM
"
+ "text": "CPU
"
}
}
}
},
- "8b6cd566a08141bd9c83d9e336752ad9": {
+ "b79f75c11fdc400eb90fdc153abd72ed": {
"kind": "Panel",
"spec": {
"display": {
- "name": "Number of VMs (some filters can not be applied atm)"
+ "name": "CPU (Including Overcommit) per Node"
},
"plugin": {
- "kind": "TimeSeriesChart",
+ "kind": "BarChart",
"spec": {
- "legend": {
- "mode": "table",
- "position": "right",
- "values": [
- "last-number"
- ]
+ "calculation": "last",
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
},
+ "groupBy": [
+ "compute_host"
+ ],
+ "isStacked": true,
+ "mode": "value",
+ "orientation": "vertical",
+ "sort": "desc",
"visual": {
- "areaOpacity": 0.3,
- "connectNulls": false,
- "display": "line",
- "lineStyle": "solid",
- "lineWidth": 1.25,
- "pointRadius": 2.75,
- "stack": "all"
- },
- "yAxis": {
- "format": {
- "shortValues": true,
- "unit": "decimal"
- },
- "label": "",
- "min": 0,
- "show": true
+ "colorOverrides": [
+ {
+ "color": "#59CC8D",
+ "regex": "Available"
+ },
+ {
+ "color": "#ff0000",
+ "regex": "Workload"
+ },
+ {
+ "color": "#EE6C6C",
+ "regex": "Reserved"
+ },
+ {
+ "color": "#FFCC00",
+ "regex": "Failover"
+ }
+ ]
}
}
},
@@ -781,8 +730,48 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "sum by(domain_name, domain_id)(max by (compute_host, domain_id, domain_name, project_id) (cortex_kvm_project_instances{availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\"}))",
- "seriesNameFormat": "{{domain_name}}"
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Available"
}
}
}
@@ -793,22 +782,41 @@
"bc51a08e10a04375833252f119c7c1f1": {
"kind": "Panel",
"spec": {
- "display": {
- "name": ""
- },
+ "display": {},
"plugin": {
"kind": "Table",
"spec": {
- "density": "standard",
- "enableFiltering": true,
"columnSettings": [
{
- "name": "compute_host",
- "header": "Host"
+ "header": "Host",
+ "name": "compute_host"
+ },
+ {
+ "cellSettings": [
+ {
+ "condition": {
+ "kind": "Value",
+ "spec": {
+ "value": "true"
+ }
+ },
+ "text": "🟢"
+ },
+ {
+ "condition": {
+ "kind": "Value",
+ "spec": {
+ "value": "false"
+ }
+ },
+ "text": "🔴"
+ }
+ ],
+ "header": "Ready",
+ "name": "ready",
+ "width": 50
},
{
- "name": "status",
- "header": "Status",
"cellSettings": [
{
"condition": {
@@ -817,8 +825,8 @@
"value": "Ready"
}
},
- "text": "Ready",
- "prefix": "🟢 "
+ "prefix": "🟢 ",
+ "text": "Ready"
},
{
"condition": {
@@ -827,8 +835,8 @@
"value": "Terminating"
}
},
- "text": "Terminating",
- "prefix": "🛑 "
+ "prefix": "🛑 ",
+ "text": "Terminating"
},
{
"condition": {
@@ -840,27 +848,27 @@
"prefix": "🚧 ",
"text": "Maintenance"
}
- ]
+ ],
+ "header": "Status",
+ "name": "status"
},
{
- "name": "availability_zone",
- "header": "Availability Zone"
+ "header": "Availability Zone",
+ "name": "availability_zone"
},
{
- "name": "building_block",
- "header": "Building Block"
+ "header": "Building Block",
+ "name": "building_block"
},
{
- "name": "cpu_architecture",
- "header": "CPU Arch."
+ "header": "CPU Arch.",
+ "name": "cpu_architecture"
},
{
- "name": "workload_type",
- "header": "Workload Type"
+ "header": "Workload Type",
+ "name": "workload_type"
},
{
- "name": "decommissioned",
- "header": "Decommissioned",
"cellSettings": [
{
"condition": {
@@ -880,11 +888,11 @@
},
"text": "❌"
}
- ]
+ ],
+ "header": "Decommissioned",
+ "name": "decommissioned"
},
{
- "name": "external_customer",
- "header": "External Customer",
"cellSettings": [
{
"condition": {
@@ -904,10 +912,18 @@
},
"text": "❌"
}
- ]
+ ],
+ "header": "External Customer",
+ "name": "external_customer"
+ },
+ {
+ "header": "OS Version",
+ "name": "os_version"
}
],
- "defaultColumnHidden": true
+ "defaultColumnHidden": true,
+ "density": "standard",
+ "enableFiltering": true
}
},
"queries": [
@@ -917,7 +933,8 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}"
+ "instant": true,
+ "query": "cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}"
}
}
}
@@ -948,7 +965,7 @@
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
- "query": "count(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", enabled=~\"$enabled\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "query": "count(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
"seriesNameFormat": "Number of Hosts"
}
}
@@ -956,6 +973,114 @@
}
]
}
+ },
+ "c61f619c77104964aa28667b95cbefb1": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "percent-decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#EE6C6C",
+ "steps": [
+ {
+ "value": 0.2
+ },
+ {
+ "color": "#59CC8D",
+ "value": 0.3
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available (%)"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "ccf9476bdfc24a18a976b7f429dd69b4": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "Number of VMs (some filters can not be applied atm)"
+ },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": {
+ "mode": "table",
+ "position": "right",
+ "values": [
+ "last-number"
+ ]
+ },
+ "visual": {
+ "areaOpacity": 0.3,
+ "connectNulls": false,
+ "display": "line",
+ "lineStyle": "solid",
+ "lineWidth": 1.25,
+ "pointRadius": 2.75,
+ "stack": "all"
+ },
+ "yAxis": {
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
+ "label": "",
+ "min": 0,
+ "show": true
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum by(domain_name, domain_id)(max by (compute_host, domain_id, domain_name, project_id) (cortex_kvm_project_instances{availability_zone=~\"$availability_zone\", workload_type=~\"$workload_type\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\"}))",
+ "seriesNameFormat": "{{domain_name}}"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "ec913f5da9a24a468f0f147d112de6bb": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "Markdown",
+ "spec": {
+ "text": "RAM
"
+ }
+ }
+ }
}
},
"layouts": [
@@ -996,6 +1121,15 @@
"$ref": "#/spec/panels/1c635e5426614893a17cdb63fcf68abd"
}
},
+ {
+ "x": 12,
+ "y": 2,
+ "width": 12,
+ "height": 10,
+ "content": {
+ "$ref": "#/spec/panels/637a56a9a10f4a01adc8fb093372e2d9"
+ }
+ },
{
"x": 0,
"y": 12,
@@ -1007,22 +1141,13 @@
},
{
"x": 6,
- "y": 12,
+ "y": 25,
"width": 6,
"height": 3,
"content": {
"$ref": "#/spec/panels/c61f619c77104964aa28667b95cbefb1"
}
},
- {
- "x": 12,
- "y": 2,
- "width": 12,
- "height": 10,
- "content": {
- "$ref": "#/spec/panels/637a56a9a10f4a01adc8fb093372e2d9"
- }
- },
{
"x": 12,
"y": 12,
@@ -1052,7 +1177,7 @@
},
{
"x": 0,
- "y": 15,
+ "y": 28,
"width": 12,
"height": 8,
"content": {
@@ -1114,33 +1239,41 @@
"items": [
{
"x": 0,
- "y": 11,
- "width": 17,
+ "y": 0,
+ "width": 24,
+ "height": 19,
+ "content": {
+ "$ref": "#/spec/panels/bc51a08e10a04375833252f119c7c1f1"
+ }
+ },
+ {
+ "x": 0,
+ "y": 19,
+ "width": 14,
"height": 9,
"content": {
"$ref": "#/spec/panels/86ea3898e16b4dd6af2476179c18ebae"
}
},
{
- "x": 17,
- "y": 11,
- "width": 7,
+ "x": 14,
+ "y": 19,
+ "width": 5,
"height": 9,
"content": {
"$ref": "#/spec/panels/c5a2806556054e15b0acace0c7bf57c2"
}
},
{
- "x": 0,
- "y": 0,
- "width": 24,
- "height": 11,
+ "x": 19,
+ "y": 19,
+ "width": 5,
+ "height": 9,
"content": {
- "$ref": "#/spec/panels/bc51a08e10a04375833252f119c7c1f1"
+ "$ref": "#/spec/panels/8cde0b45349d4b019aca4cea79627980"
}
}
- ],
- "repeatVariable": ""
+ ]
}
},
{
@@ -1157,7 +1290,7 @@
"x": 0,
"y": 0,
"width": 24,
- "height": 9,
+ "height": 33,
"content": {
"$ref": "#/spec/panels/45677dc08c73425cb3ca1422aec50f15"
}
@@ -1287,23 +1420,24 @@
"kind": "ListVariable",
"spec": {
"display": {
- "name": "Enabled",
+ "name": "Ready",
"hidden": false
},
"defaultValue": "$__all",
"allowAllValue": true,
"allowMultiple": false,
+ "customAllValue": ".*",
"sort": "none",
"plugin": {
"kind": "PrometheusLabelValuesVariable",
"spec": {
- "labelName": "enabled",
+ "labelName": "ready",
"matchers": [
"cortex_kvm_host_capacity_total"
]
}
},
- "name": "enabled"
+ "name": "ready"
}
},
{
@@ -1378,7 +1512,6 @@
{
"kind": "ListVariable",
"spec": {
- "name": "status",
"display": {
"name": "Status",
"hidden": false
@@ -1396,7 +1529,8 @@
"cortex_kvm_host_capacity_total{availability_zone=~\"$availability_zone\"}"
]
}
- }
+ },
+ "name": "status"
}
}
],
@@ -1404,7 +1538,6 @@
"duration": "7d",
"timezone": "local",
"refreshInterval": "60s",
- "datasources": {},
"links": []
}
}
\ No newline at end of file
diff --git a/dashboards/cortex-compute-kvm-overview.json b/dashboards/cortex-compute-kvm-overview.json
new file mode 100644
index 000000000..06e616c2d
--- /dev/null
+++ b/dashboards/cortex-compute-kvm-overview.json
@@ -0,0 +1,1603 @@
+{
+ "kind": "Dashboard",
+ "metadata": {
+ "name": "cortex_compute_kvm_-_overview",
+ "createdAt": "2026-08-07T12:22:15.443047024Z",
+ "updatedAt": "2026-10-16T08:40:03.66409193Z",
+ "version": 27,
+ "tags": [
+ "compute",
+ "infrastructure",
+ "kvm"
+ ],
+ "project": "workload-management"
+ },
+ "spec": {
+ "display": {
+ "name": "Cortex Compute KVM - Overview"
+ },
+ "panels": {
+ "0bd008cb61534dd79adf132c4dbf4cbe": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "percent-decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#EE6C6C",
+ "steps": [
+ {
+ "value": 0.2
+ },
+ {
+ "color": "#59CC8D",
+ "value": 0.3
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available (%)"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "21f4f235214c41e3af992369cd123460": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "CPU (Including Overcommit) per Node"
+ },
+ "plugin": {
+ "kind": "BarChart",
+ "spec": {
+ "calculation": "last",
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
+ "groupBy": [
+ "compute_host"
+ ],
+ "isStacked": true,
+ "mode": "value",
+ "orientation": "vertical",
+ "sort": "desc",
+ "visual": {
+ "colorOverrides": [
+ {
+ "color": "#59CC8D",
+ "regex": "Available"
+ },
+ {
+ "color": "#ff0000",
+ "regex": "Workload"
+ },
+ {
+ "color": "#EE6C6C",
+ "regex": "Reserved"
+ },
+ {
+ "color": "#FFCC00",
+ "regex": "Failover"
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "25d5d82a303d4996ac8c54221a2c7930": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "percent-decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#EE6C6C",
+ "steps": [
+ {
+ "value": 0.2
+ },
+ {
+ "color": "#59CC8D",
+ "value": 0.3
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available (%)"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "3fa162da513245e48dc59a999f199b06": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "RAM"
+ },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": {
+ "mode": "table",
+ "position": "bottom",
+ "size": "small",
+ "values": [
+ "last"
+ ]
+ },
+ "querySettings": [
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#ff0000",
+ "queryIndex": 0
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#EE6C6C",
+ "queryIndex": 1
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#FFCC00",
+ "queryIndex": 2
+ },
+ {
+ "colorMode": "fixed-single",
+ "colorValue": "#59CC8D",
+ "lineStyle": "dotted",
+ "queryIndex": 3
+ }
+ ],
+ "visual": {
+ "areaOpacity": 0.3,
+ "connectNulls": false,
+ "display": "line",
+ "lineStyle": "solid",
+ "lineWidth": 1.25,
+ "pointRadius": 2.75,
+ "stack": "all"
+ },
+ "yAxis": {
+ "format": {
+ "unit": "bytes"
+ },
+ "label": "",
+ "min": 0,
+ "show": true
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "637a56a9a10f4a01adc8fb093372e2d9": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "RAM"
+ },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": {
+ "mode": "table",
+ "position": "bottom",
+ "size": "small",
+ "values": [
+ "last"
+ ]
+ },
+ "querySettings": [
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#ff0000",
+ "queryIndex": 0
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#EE6C6C",
+ "queryIndex": 1
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#FFCC00",
+ "queryIndex": 2
+ },
+ {
+ "colorMode": "fixed-single",
+ "colorValue": "#59CC8D",
+ "lineStyle": "dotted",
+ "queryIndex": 3
+ }
+ ],
+ "visual": {
+ "areaOpacity": 0.3,
+ "connectNulls": false,
+ "display": "line",
+ "lineStyle": "solid",
+ "lineWidth": 1.25,
+ "pointRadius": 2.75,
+ "stack": "all"
+ },
+ "yAxis": {
+ "format": {
+ "unit": "bytes"
+ },
+ "label": "",
+ "min": 0,
+ "show": true
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "67452971dc324dc895af19b9d53a534c": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "percent-decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#EE6C6C",
+ "steps": [
+ {
+ "value": 0.2
+ },
+ {
+ "color": "#59CC8D",
+ "value": 0.3
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available (%)"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "70c5f4938c6949cd8134e0cc4141b114": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "bytes"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#a8a8a8"
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Total"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "8903f96391d145edb726911120d44467": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#a8a8a8"
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Total"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "89954bf09009496dbbef7d0187f0a6f1": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "RAM per Node (HANA Stacking)",
+ "description": "This panel shows the stacking of the HANA VMs."
+ },
+ "plugin": {
+ "kind": "BarChart",
+ "spec": {
+ "calculation": "last",
+ "format": {
+ "unit": "bytes"
+ },
+ "groupBy": [
+ "compute_host"
+ ],
+ "isStacked": true,
+ "mode": "value",
+ "orientation": "vertical",
+ "sort": "desc",
+ "visual": {
+ "colorOverrides": [
+ {
+ "color": "#ffffff",
+ "regex": "Available"
+ },
+ {
+ "color": "#ff0000",
+ "regex": "Workload"
+ },
+ {
+ "color": "#ababab",
+ "regex": "Reserved"
+ },
+ {
+ "color": "#838383",
+ "regex": "Failover"
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum by (compute_host, project_name, domain_name) (cortex_kvm_hana_stacking_ram_bytes{availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"})",
+ "seriesNameFormat": "{{domain_name}} / {{project_name}}"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "904dcac2a8e14775a4d8ae2ca2202938": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "RAM per Node"
+ },
+ "plugin": {
+ "kind": "BarChart",
+ "spec": {
+ "calculation": "last",
+ "format": {
+ "unit": "bytes"
+ },
+ "groupBy": [
+ "compute_host"
+ ],
+ "isStacked": true,
+ "mode": "value",
+ "orientation": "vertical",
+ "sort": "desc",
+ "visual": {
+ "colorOverrides": [
+ {
+ "color": "#59CC8D",
+ "regex": "Available"
+ },
+ {
+ "color": "#ff0000",
+ "regex": "Workload"
+ },
+ {
+ "color": "#EE6C6C",
+ "regex": "Reserved"
+ },
+ {
+ "color": "#FFCC00",
+ "regex": "Failover"
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "a9dd34da3a4041c7ba68744a1575b517": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#a8a8a8"
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"cpu\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Total"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "aaf7a61a2049412489127df64eeb9482": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "CPU (Including Overcommit)"
+ },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": {
+ "mode": "table",
+ "position": "bottom",
+ "size": "small",
+ "values": [
+ "last"
+ ]
+ },
+ "querySettings": [
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#ff0000",
+ "queryIndex": 0
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#EE6C6C",
+ "queryIndex": 1
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#FFCC00",
+ "queryIndex": 2
+ },
+ {
+ "colorMode": "fixed-single",
+ "colorValue": "#59CC8D",
+ "lineStyle": "dotted",
+ "queryIndex": 3
+ }
+ ],
+ "visual": {
+ "areaOpacity": 0.3,
+ "connectNulls": false,
+ "display": "line",
+ "lineStyle": "solid",
+ "lineWidth": 1.25,
+ "pointRadius": 2.75,
+ "stack": "all"
+ },
+ "yAxis": {
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
+ "label": "",
+ "min": 0,
+ "show": true
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "cb02b2ba990c463db72337b2eede7489": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "CPU (Including Overcommit) per Node"
+ },
+ "plugin": {
+ "kind": "BarChart",
+ "spec": {
+ "calculation": "last",
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
+ "groupBy": [
+ "compute_host"
+ ],
+ "isStacked": true,
+ "mode": "value",
+ "orientation": "vertical",
+ "sort": "desc",
+ "visual": {
+ "colorOverrides": [
+ {
+ "color": "#59CC8D",
+ "regex": "Available"
+ },
+ {
+ "color": "#ff0000",
+ "regex": "Workload"
+ },
+ {
+ "color": "#EE6C6C",
+ "regex": "Reserved"
+ },
+ {
+ "color": "#FFCC00",
+ "regex": "Failover"
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"hana\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "e2e97cc902fd4754bbc907ca87952e24": {
+ "kind": "Panel",
+ "spec": {
+ "display": {
+ "name": "CPU (Including Overcommit)"
+ },
+ "plugin": {
+ "kind": "TimeSeriesChart",
+ "spec": {
+ "legend": {
+ "mode": "table",
+ "position": "bottom",
+ "size": "small",
+ "values": [
+ "last"
+ ]
+ },
+ "querySettings": [
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#ff0000",
+ "queryIndex": 0
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#EE6C6C",
+ "queryIndex": 1
+ },
+ {
+ "areaOpacity": 1,
+ "colorMode": "fixed-single",
+ "colorValue": "#FFCC00",
+ "queryIndex": 2
+ },
+ {
+ "colorMode": "fixed-single",
+ "colorValue": "#59CC8D",
+ "lineStyle": "dotted",
+ "queryIndex": 3
+ }
+ ],
+ "visual": {
+ "areaOpacity": 0.3,
+ "connectNulls": false,
+ "display": "line",
+ "lineStyle": "solid",
+ "lineWidth": 1.25,
+ "pointRadius": 2.75,
+ "stack": "all"
+ },
+ "yAxis": {
+ "format": {
+ "shortValues": true,
+ "unit": "decimal"
+ },
+ "label": "",
+ "min": 0,
+ "show": true
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"utilized\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Workload"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"reserved\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Reserved"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"failover\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Failover"
+ }
+ }
+ }
+ },
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"cpu\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "f99f5bdcf0254d7c8b3396ca5e3bcc9f": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "bytes"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#a8a8a8"
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Total"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "ff12a8beff3e493e86239d0d65386ef7": {
+ "kind": "Panel",
+ "spec": {
+ "display": {},
+ "plugin": {
+ "kind": "StatChart",
+ "spec": {
+ "calculation": "last-number",
+ "colorMode": "background_solid",
+ "format": {
+ "unit": "percent-decimal"
+ },
+ "legendMode": "on",
+ "thresholds": {
+ "defaultColor": "#EE6C6C",
+ "steps": [
+ {
+ "value": 0.2
+ },
+ {
+ "color": "#59CC8D",
+ "value": 0.3
+ }
+ ]
+ }
+ }
+ },
+ "queries": [
+ {
+ "kind": "TimeSeriesQuery",
+ "spec": {
+ "plugin": {
+ "kind": "PrometheusTimeSeriesQuery",
+ "spec": {
+ "instant": true,
+ "query": "sum(max(cortex_kvm_host_capacity_usage{resource=\"ram\", type=\"available\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host)) /\nsum(max(cortex_kvm_host_capacity_total{resource=\"ram\", availability_zone=~\"$availability_zone\", workload_type=\"general-purpose\", cpu_architecture=~\"$cpu_architecture\", building_block=~\"$building_block\", compute_host=~\"$compute_host\", ready=~\"$ready\", maintenance=~\"$maintenance\", decommissioned=~\"$decommissioned\", external_customer=~\"$external_customer\", status=~\"$status\"}) by (compute_host))",
+ "seriesNameFormat": "Available (%)"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "layouts": [
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "HANA",
+ "collapse": {
+ "open": true
+ }
+ },
+ "items": [
+ {
+ "x": 0,
+ "y": 0,
+ "width": 12,
+ "height": 10,
+ "content": {
+ "$ref": "#/spec/panels/aaf7a61a2049412489127df64eeb9482"
+ }
+ },
+ {
+ "x": 12,
+ "y": 0,
+ "width": 12,
+ "height": 10,
+ "content": {
+ "$ref": "#/spec/panels/3fa162da513245e48dc59a999f199b06"
+ }
+ },
+ {
+ "x": 0,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/8903f96391d145edb726911120d44467"
+ }
+ },
+ {
+ "x": 6,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/25d5d82a303d4996ac8c54221a2c7930"
+ }
+ },
+ {
+ "x": 12,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/70c5f4938c6949cd8134e0cc4141b114"
+ }
+ },
+ {
+ "x": 18,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/67452971dc324dc895af19b9d53a534c"
+ }
+ },
+ {
+ "x": 0,
+ "y": 13,
+ "width": 12,
+ "height": 8,
+ "content": {
+ "$ref": "#/spec/panels/cb02b2ba990c463db72337b2eede7489"
+ }
+ },
+ {
+ "x": 12,
+ "y": 13,
+ "width": 12,
+ "height": 8,
+ "content": {
+ "$ref": "#/spec/panels/89954bf09009496dbbef7d0187f0a6f1"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "kind": "Grid",
+ "spec": {
+ "display": {
+ "title": "General Purpose",
+ "collapse": {
+ "open": true
+ }
+ },
+ "items": [
+ {
+ "x": 0,
+ "y": 0,
+ "width": 12,
+ "height": 10,
+ "content": {
+ "$ref": "#/spec/panels/e2e97cc902fd4754bbc907ca87952e24"
+ }
+ },
+ {
+ "x": 12,
+ "y": 0,
+ "width": 12,
+ "height": 10,
+ "content": {
+ "$ref": "#/spec/panels/637a56a9a10f4a01adc8fb093372e2d9"
+ }
+ },
+ {
+ "x": 0,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/a9dd34da3a4041c7ba68744a1575b517"
+ }
+ },
+ {
+ "x": 6,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/0bd008cb61534dd79adf132c4dbf4cbe"
+ }
+ },
+ {
+ "x": 12,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/f99f5bdcf0254d7c8b3396ca5e3bcc9f"
+ }
+ },
+ {
+ "x": 18,
+ "y": 10,
+ "width": 6,
+ "height": 3,
+ "content": {
+ "$ref": "#/spec/panels/ff12a8beff3e493e86239d0d65386ef7"
+ }
+ },
+ {
+ "x": 0,
+ "y": 13,
+ "width": 12,
+ "height": 8,
+ "content": {
+ "$ref": "#/spec/panels/21f4f235214c41e3af992369cd123460"
+ }
+ },
+ {
+ "x": 12,
+ "y": 13,
+ "width": 12,
+ "height": 8,
+ "content": {
+ "$ref": "#/spec/panels/904dcac2a8e14775a4d8ae2ca2202938"
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "variables": [
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Availability Zone",
+ "hidden": false
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": true,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "availability_zone",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "availability_zone"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Workload Type",
+ "description": "HANA / General Purpose",
+ "hidden": true
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": false,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "workload_type",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "workload_type"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "CPU Architecture",
+ "hidden": false
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": false,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "cpu_architecture",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "cpu_architecture"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Building Block",
+ "hidden": false
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": true,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "building_block",
+ "matchers": [
+ "cortex_kvm_host_capacity_total{availability_zone=~\"$availability_zone\"}"
+ ]
+ }
+ },
+ "name": "building_block"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Nodes",
+ "hidden": false
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": true,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "compute_host",
+ "matchers": [
+ "cortex_kvm_host_capacity_total{building_block=~\"$building_block\",availability_zone=~\"$availability_zone\"}"
+ ]
+ }
+ },
+ "name": "compute_host"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Ready",
+ "hidden": false
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": false,
+ "customAllValue": ".*",
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "ready",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "ready"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Maintenance",
+ "hidden": false
+ },
+ "defaultValue": "false",
+ "allowAllValue": true,
+ "allowMultiple": false,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "maintenance",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "maintenance"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Decommissioned",
+ "hidden": false
+ },
+ "defaultValue": "false",
+ "allowAllValue": true,
+ "allowMultiple": false,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "decommissioned",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "decommissioned"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "External Customer",
+ "hidden": true
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": false,
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "external_customer",
+ "matchers": [
+ "cortex_kvm_host_capacity_total"
+ ]
+ }
+ },
+ "name": "external_customer"
+ }
+ },
+ {
+ "kind": "ListVariable",
+ "spec": {
+ "display": {
+ "name": "Status",
+ "hidden": false
+ },
+ "defaultValue": "$__all",
+ "allowAllValue": true,
+ "allowMultiple": true,
+ "customAllValue": ".*",
+ "sort": "none",
+ "plugin": {
+ "kind": "PrometheusLabelValuesVariable",
+ "spec": {
+ "labelName": "status",
+ "matchers": [
+ "cortex_kvm_host_capacity_total{availability_zone=~\"$availability_zone\"}"
+ ]
+ }
+ },
+ "name": "status"
+ }
+ }
+ ],
+ "annotations": [],
+ "duration": "7d",
+ "timezone": "local",
+ "refreshInterval": "60s",
+ "links": []
+ }
+}
\ No newline at end of file
diff --git a/tools/perses/dashboards/cortex-compute-vmware-details.json b/dashboards/cortex-compute-vmware-details.json
similarity index 100%
rename from tools/perses/dashboards/cortex-compute-vmware-details.json
rename to dashboards/cortex-compute-vmware-details.json
diff --git a/tools/perses/dashboards/cortex-compute-vmware-overview.json b/dashboards/cortex-compute-vmware-overview.json
similarity index 100%
rename from tools/perses/dashboards/cortex-compute-vmware-overview.json
rename to dashboards/cortex-compute-vmware-overview.json
diff --git a/tools/perses/dashboards/cortex-global-compute-capacity.json b/dashboards/cortex-global-compute-capacity.json
similarity index 100%
rename from tools/perses/dashboards/cortex-global-compute-capacity.json
rename to dashboards/cortex-global-compute-capacity.json
diff --git a/tools/perses/dashboards/cortex-placement-shim-status.json b/dashboards/cortex-placement-shim-status.json
similarity index 100%
rename from tools/perses/dashboards/cortex-placement-shim-status.json
rename to dashboards/cortex-placement-shim-status.json
diff --git a/tools/perses/dashboards/cortex-project-flavor-usage.json b/dashboards/cortex-project-flavor-usage.json
similarity index 100%
rename from tools/perses/dashboards/cortex-project-flavor-usage.json
rename to dashboards/cortex-project-flavor-usage.json
diff --git a/tools/perses/dashboards/cortex-status-global.json b/dashboards/cortex-status-global.json
similarity index 100%
rename from tools/perses/dashboards/cortex-status-global.json
rename to dashboards/cortex-status-global.json
diff --git a/tools/perses/dashboards/cortex-status.json b/dashboards/cortex-status.json
similarity index 100%
rename from tools/perses/dashboards/cortex-status.json
rename to dashboards/cortex-status.json
diff --git a/tools/perses/dashboards/project.json b/dashboards/project.json
similarity index 100%
rename from tools/perses/dashboards/project.json
rename to dashboards/project.json
diff --git a/docs/01-getting-started/07-tooling.md b/docs/01-getting-started/07-tooling.md
index e18e75d20..ae2be0aff 100644
--- a/docs/01-getting-started/07-tooling.md
+++ b/docs/01-getting-started/07-tooling.md
@@ -8,10 +8,10 @@
The `tools/` directory holds standalone developer and operator utilities — not shipped in the Helm
charts, run by hand during development, debugging, or dashboard work. Each is a small Go program you
-run with `go run` from the repository root (except the two dashboard directories, which hold static
-definitions). This page documents each one in turn, with an illustrative slice of its output where the
-tool prints a report. The example outputs are hand-authored to show the shape of what each tool prints;
-they are not captured from a live run.
+run with `go run` from the repository root (except `tools/perses`, which holds Helm values rather than
+a runnable program). This page documents each one in turn, with an illustrative slice of its output
+where the tool prints a report. The example outputs are hand-authored to show the shape of what each
+tool prints; they are not captured from a live run.
## `tools/spawner` — synthetic workload generator
@@ -200,10 +200,10 @@ Output of pipeline : host-a, host-b
Final output : host-a, host-b
```
-## `tools/perses` — dashboard definitions
+## `tools/perses` — Perses Helm values
-This is **not** a runnable Go program. `tools/perses` holds Perses dashboard definitions (JSON) rendered by a
-Perses instance. They are covered in
+`tools/perses` holds Perses Helm values (`values.yaml`). The Perses dashboard definitions (JSON) have
+moved to the repo-root `dashboards/` directory and are covered in
[The infrastructure dashboard](../04-knowledge-database/05-infrastructure-dashboard.md).
These utilities sit beside Cortex rather than inside it: none is shipped in the Helm charts, and each
diff --git a/docs/04-knowledge-database/05-infrastructure-dashboard.md b/docs/04-knowledge-database/05-infrastructure-dashboard.md
index 3ee74dc31..8cef52c1d 100644
--- a/docs/04-knowledge-database/05-infrastructure-dashboard.md
+++ b/docs/04-knowledge-database/05-infrastructure-dashboard.md
@@ -9,7 +9,7 @@
The `infrastructure` KPI family exists for a specific audience: capacity planners and operators who need to
*see* the fleet — where headroom is, how projects stack up, whether HANA workloads are placed the way policy
wants. This page covers those KPIs and the dashboards that render them, which — unlike most Cortex config —
-live in `tools/`, not in the Helm charts.
+live in `dashboards/`, not in the Helm charts.
## Infrastructure KPIs: fleet-level planning signals
@@ -30,21 +30,23 @@ convention (KVM compute, VMware compute, VMware Ironic) so the metrics carry the
Being ordinary Prometheus metrics on the manager `/metrics` endpoint ([previous page](04-kpis-and-metrics-api.md)),
they need no special plumbing to reach a dashboard.
-## Dashboards live in `tools/`, not in Helm
+## Dashboards live in `dashboards/`, not in Helm
The dashboard definitions are development/operations tooling, deliberately kept out of the deployable charts:
-- **Perses** — `tools/perses/dashboards/`, including `cortex-compute-kvm-overview.json` (the compute KVM
- overview), `cortex-placement-shim-status.json` (the
- [shim](../03-reservations-and-inventory/05-placement-api-shim.md) status board), `cortex-status.json`,
- the compute VMware boards (`cortex-compute-vmware-overview.json`, `cortex-compute-vmware-details.json`),
- and `project.json`.
+- **Perses** — `dashboards/`, including `cortex-compute-kvm-overview.json` and
+ `cortex-compute-kvm-details.json` (the compute KVM boards),
+ `cortex-compute-vmware-overview.json` and `cortex-compute-vmware-details.json` (the compute VMware
+ boards), `cortex-global-compute-capacity.json`, `cortex-placement-shim-status.json` (the
+ [shim](../03-reservations-and-inventory/05-placement-api-shim.md) status board),
+ `cortex-project-flavor-usage.json`, `cortex-status-global.json`, `cortex-status.json`, and
+ `project.json`.
> [!NOTE]
> There are no dashboard JSON files under `helm/`. The only `dashboard:` strings in the charts are
-> alert-rule annotations pointing at dashboards by name — the definitions themselves ship in `tools/`. To
-> use one, import it into your own Perses instance; Cortex does not deploy a dashboard server for
-> you.
+> alert-rule annotations pointing at dashboards by name — the definitions themselves ship in
+> `dashboards/`. To use one, import it into your own Perses instance; Cortex does not deploy a
+> dashboard server for you.
This page is the visual read-out of the whole [knowledge flow](01-knowledge-flow-overview.md): datasources
cache the world, extractors derive features, the `infrastructure` KPIs turn those features into fleet-level
diff --git a/docs/04-knowledge-database/06-monitoring.md b/docs/04-knowledge-database/06-monitoring.md
index e104f28e5..318ec7091 100644
--- a/docs/04-knowledge-database/06-monitoring.md
+++ b/docs/04-knowledge-database/06-monitoring.md
@@ -85,7 +85,7 @@ Expected — the `PrometheusRule` containing the `cortex-nova-alerts` group.
Dashboards are not part of the Helm charts. Import them from the repository:
-- Perses: `tools/perses/dashboards/`
+- Perses: `dashboards/`
These include a compute KVM overview and a placement-shim status dashboard, and are covered in
[The infrastructure dashboard](05-infrastructure-dashboard.md).
diff --git a/go.mod b/go.mod
index 6fe8d6601..e8643f3be 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/cobaltcore-dev/cortex
go 1.27
require (
- github.com/cobaltcore-dev/openstack-hypervisor-operator v1.2.3
- github.com/cobaltcore-dev/vm-operator v0.2.9
+ github.com/cobaltcore-dev/openstack-hypervisor-operator v1.3.0
+ github.com/cobaltcore-dev/vm-operator v0.2.10
github.com/go-gorp/gorp v2.2.0+incompatible
github.com/go-logr/logr v1.4.4
github.com/google/uuid v1.6.0
@@ -16,7 +16,7 @@ require (
github.com/prometheus/client_golang v1.25.0
github.com/prometheus/client_model v0.6.3
github.com/sapcc/go-api-declarations v1.25.1
- github.com/sapcc/go-bits v0.0.0-20261013222358-416f3ade2c19
+ github.com/sapcc/go-bits v0.0.0-20261023092235-568ad9d4c1f2
go.uber.org/zap v1.28.0
go.xyrillian.de/gg v1.19.0
golang.org/x/sync v0.24.0
@@ -126,7 +126,7 @@ require (
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
k8s.io/streaming v0.37.1 // indirect
- k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect
+ k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 2d380f3ad..ec102d933 100644
--- a/go.sum
+++ b/go.sum
@@ -22,10 +22,10 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cobaltcore-dev/openstack-hypervisor-operator v1.2.3 h1:2KjrD+LzU8pgvm1HEIEw5qlRF1gcbFp7Q/t9z+AnY4g=
-github.com/cobaltcore-dev/openstack-hypervisor-operator v1.2.3/go.mod h1:DyQ6MO1TsC4kzeG3l8tnIXQVeA/PITm22RyitJhDaKI=
-github.com/cobaltcore-dev/vm-operator v0.2.9 h1:1aSFkGY0VEyBkx+8xtY/JJQ9OHLY1CCIVYBbSVXhYPI=
-github.com/cobaltcore-dev/vm-operator v0.2.9/go.mod h1:0Lanmhb7onfOIHX+4h1lGLC7vga/nNYabJ6a/y/APk4=
+github.com/cobaltcore-dev/openstack-hypervisor-operator v1.3.0 h1:WhvPRxNzZCL0X+/k+H2VZPO2jtzGXQ/SspIuYqpryzQ=
+github.com/cobaltcore-dev/openstack-hypervisor-operator v1.3.0/go.mod h1:vKyl1a9EJDJs24Uh8GcSfij0WwNXBo9/wtkTG5SOVwI=
+github.com/cobaltcore-dev/vm-operator v0.2.10 h1:W/cNZBDva2pq9kn4QYTcfn4VPxyNEoYqxJBU9hc8Ulg=
+github.com/cobaltcore-dev/vm-operator v0.2.10/go.mod h1:0Lanmhb7onfOIHX+4h1lGLC7vga/nNYabJ6a/y/APk4=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -155,10 +155,10 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/onsi/ginkgo/v2 v2.32.2 h1:2o6vyFvR6snrJWgRVztC+OwuqqPEMI1UzYl2s2iU7Cg=
-github.com/onsi/ginkgo/v2 v2.32.2/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
-github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU=
-github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
+github.com/onsi/ginkgo/v2 v2.33.1 h1:62XfWJbEIjPSCSDYrsCPP/SHcmH7O0M8ne8zfqQ2iz4=
+github.com/onsi/ginkgo/v2 v2.33.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
+github.com/onsi/gomega v1.44.0 h1:eAiGl3Pw5jz5GQdDff0BcxYpAX1JxW8xD7mFUuwNfZQ=
+github.com/onsi/gomega v1.44.0/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -187,8 +187,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sapcc/go-api-declarations v1.25.1 h1:Lrbf8TZY/ARg0J9IrlrObqnXjrzSzmYKQd/L/a71Lsg=
github.com/sapcc/go-api-declarations v1.25.1/go.mod h1:bJkK8wphzlmUmN5lxBv8BbfxUXMOkW+UiC10xBHXsFs=
-github.com/sapcc/go-bits v0.0.0-20261013222358-416f3ade2c19 h1:JKZe05UIn70AalARFEN5IKSh7yhb6wPCEUNn/41VNM8=
-github.com/sapcc/go-bits v0.0.0-20261013222358-416f3ade2c19/go.mod h1:Z2uDp3zty2oJAXAVZZaUazo3uGZ6G2zBfauNtEl+EHw=
+github.com/sapcc/go-bits v0.0.0-20261023092235-568ad9d4c1f2 h1:wbji0B3aQIzAWXx9qZfx3YNGttRffUvNN02TWpO4H8o=
+github.com/sapcc/go-bits v0.0.0-20261023092235-568ad9d4c1f2/go.mod h1:ZiQj8yOMniyYxmkMPJin1p/juJtM0UVXweOZRw/9oQs=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
@@ -303,8 +303,8 @@ k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3d
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
k8s.io/streaming v0.37.1 h1:TpzVfQeFuVndn2g9mFqxy1UcUYPwDzqjUmwR/IzJCWc=
k8s.io/streaming v0.37.1/go.mod h1:APlJR26ZWRcVy5bIEj0QRrKUXROtBHPcxl2NT7EAzPU=
-k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs=
-k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
+k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0 h1:/YpDJ4vReG7ZmzSpBGxduXgywWkJU9zHubgJG03MT+Y=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.36.0/go.mod h1:tJo1aepTXyR+8Xs3sUsGBDk4Ub2AM5dPAPKJx0mpm5c=
sigs.k8s.io/controller-runtime v0.25.2 h1:bEkK3PVOIVK9X8QWLGVhJgmFc++47vfT6wakSzAOLsQ=
diff --git a/helm/bundles/cortex-cinder/Chart.yaml b/helm/bundles/cortex-cinder/Chart.yaml
index c43ba2f00..b78ae3a86 100644
--- a/helm/bundles/cortex-cinder/Chart.yaml
+++ b/helm/bundles/cortex-cinder/Chart.yaml
@@ -5,23 +5,23 @@ apiVersion: v2
name: cortex-cinder
description: A Helm chart deploying Cortex for Cinder.
type: application
-version: 0.0.108
+version: 0.0.109
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex-postgres
- name: cortex-postgres
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.6.28
+ version: 0.6.29
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
alias: cortex-knowledge-controllers
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
alias: cortex-scheduling-controllers
# Owner info adds a configmap to the kubernetes cluster with information on
diff --git a/helm/bundles/cortex-crds/Chart.yaml b/helm/bundles/cortex-crds/Chart.yaml
index 459bc2501..54f807f46 100644
--- a/helm/bundles/cortex-crds/Chart.yaml
+++ b/helm/bundles/cortex-crds/Chart.yaml
@@ -5,13 +5,13 @@ apiVersion: v2
name: cortex-crds
description: A Helm chart deploying Cortex CRDs.
type: application
-version: 0.0.108
+version: 0.0.109
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
# Owner info adds a configmap to the kubernetes cluster with information on
# the service owner. This makes it easier to find out who to contact in case
diff --git a/helm/bundles/cortex-ironcore/Chart.yaml b/helm/bundles/cortex-ironcore/Chart.yaml
index 445cd7038..ff3435d3e 100644
--- a/helm/bundles/cortex-ironcore/Chart.yaml
+++ b/helm/bundles/cortex-ironcore/Chart.yaml
@@ -5,13 +5,13 @@ apiVersion: v2
name: cortex-ironcore
description: A Helm chart deploying Cortex for IronCore.
type: application
-version: 0.0.108
+version: 0.0.109
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
# Owner info adds a configmap to the kubernetes cluster with information on
# the service owner. This makes it easier to find out who to contact in case
diff --git a/helm/bundles/cortex-manila/Chart.yaml b/helm/bundles/cortex-manila/Chart.yaml
index 525936c96..61aa37d44 100644
--- a/helm/bundles/cortex-manila/Chart.yaml
+++ b/helm/bundles/cortex-manila/Chart.yaml
@@ -5,23 +5,23 @@ apiVersion: v2
name: cortex-manila
description: A Helm chart deploying Cortex for Manila.
type: application
-version: 0.0.108
+version: 0.0.109
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex-postgres
- name: cortex-postgres
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.6.28
+ version: 0.6.29
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
alias: cortex-knowledge-controllers
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
alias: cortex-scheduling-controllers
# Owner info adds a configmap to the kubernetes cluster with information on
diff --git a/helm/bundles/cortex-nova/Chart.yaml b/helm/bundles/cortex-nova/Chart.yaml
index b26a23d2c..0ed98c9d1 100644
--- a/helm/bundles/cortex-nova/Chart.yaml
+++ b/helm/bundles/cortex-nova/Chart.yaml
@@ -5,23 +5,23 @@ apiVersion: v2
name: cortex-nova
description: A Helm chart deploying Cortex for Nova.
type: application
-version: 0.0.108
+version: 0.0.109
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex-postgres
- name: cortex-postgres
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.6.28
+ version: 0.6.29
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
alias: cortex-knowledge-controllers
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
alias: cortex-scheduling-controllers
# Owner info adds a configmap to the kubernetes cluster with information on
diff --git a/helm/bundles/cortex-placement-shim/Chart.yaml b/helm/bundles/cortex-placement-shim/Chart.yaml
index c0d19b9cb..b9b3f783c 100644
--- a/helm/bundles/cortex-placement-shim/Chart.yaml
+++ b/helm/bundles/cortex-placement-shim/Chart.yaml
@@ -5,13 +5,13 @@ apiVersion: v2
name: cortex-placement-shim
description: A Helm chart deploying the Cortex placement shim.
type: application
-version: 0.1.33
+version: 0.1.34
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex-shim
- name: cortex-shim
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.1.33
+ version: 0.1.34
# Owner info adds a configmap to the kubernetes cluster with information on
# the service owner. This makes it easier to find out who to contact in case
# of issues. See: https://github.com/sapcc/helm-charts/pkgs/container/helm-charts%2Fowner-info
diff --git a/helm/bundles/cortex-pods/Chart.yaml b/helm/bundles/cortex-pods/Chart.yaml
index 91dc12d98..4f565f388 100644
--- a/helm/bundles/cortex-pods/Chart.yaml
+++ b/helm/bundles/cortex-pods/Chart.yaml
@@ -5,13 +5,13 @@ apiVersion: v2
name: cortex-pods
description: A Helm chart deploying Cortex for Pods.
type: application
-version: 0.0.108
+version: 0.0.109
appVersion: 0.1.0
dependencies:
# from: file://../../library/cortex
- name: cortex
repository: oci://ghcr.io/cobaltcore-dev/cortex/charts
- version: 0.7.1
+ version: 0.7.2
# Owner info adds a configmap to the kubernetes cluster with information on
# the service owner. This makes it easier to find out who to contact in case
diff --git a/helm/dev/cortex-prometheus-operator/Chart.yaml b/helm/dev/cortex-prometheus-operator/Chart.yaml
index 8805a7699..650a960aa 100644
--- a/helm/dev/cortex-prometheus-operator/Chart.yaml
+++ b/helm/dev/cortex-prometheus-operator/Chart.yaml
@@ -10,4 +10,4 @@ dependencies:
# CRDs of the prometheus operator, such as PrometheusRule, ServiceMonitor, etc.
- name: kube-prometheus-stack
repository: oci://ghcr.io/prometheus-community/charts
- version: 92.10.1
+ version: 92.13.1
diff --git a/helm/library/cortex-postgres/Chart.yaml b/helm/library/cortex-postgres/Chart.yaml
index 913ecbd1a..ccccc54d2 100644
--- a/helm/library/cortex-postgres/Chart.yaml
+++ b/helm/library/cortex-postgres/Chart.yaml
@@ -5,5 +5,5 @@ apiVersion: v2
name: cortex-postgres
description: Postgres setup for Cortex.
type: application
-version: 0.6.28
-appVersion: "sha-dbe244dd"
+version: 0.6.29
+appVersion: "sha-4f5a623f"
diff --git a/helm/library/cortex-shim/Chart.yaml b/helm/library/cortex-shim/Chart.yaml
index abc48c0a6..e18aa113e 100644
--- a/helm/library/cortex-shim/Chart.yaml
+++ b/helm/library/cortex-shim/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
name: cortex-shim
description: A Helm chart to distribute cortex shims.
type: application
-version: 0.1.33
-appVersion: "sha-97182126"
+version: 0.1.34
+appVersion: "sha-eac4ca69"
icon: "https://example.com/icon.png"
dependencies: []
diff --git a/helm/library/cortex/Chart.yaml b/helm/library/cortex/Chart.yaml
index e9f93831c..ddf9b080f 100644
--- a/helm/library/cortex/Chart.yaml
+++ b/helm/library/cortex/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
name: cortex
description: A Helm chart to distribute cortex.
type: application
-version: 0.7.1
-appVersion: "sha-3e478f14"
+version: 0.7.2
+appVersion: "sha-ece8785b"
icon: "https://example.com/icon.png"
dependencies: []
diff --git a/helm/library/cortex/files/crds/cortex.cloud_reservations.yaml b/helm/library/cortex/files/crds/cortex.cloud_reservations.yaml
index efd4efb56..a9bdceebf 100644
--- a/helm/library/cortex/files/crds/cortex.cloud_reservations.yaml
+++ b/helm/library/cortex/files/crds/cortex.cloud_reservations.yaml
@@ -26,6 +26,7 @@ spec:
type: string
- jsonPath: .spec.committedResourceReservation.resourceGroup
name: ResourceGroup
+ priority: 1
type: string
- jsonPath: .spec.failoverReservation.resourceGroup
name: HA ResourceGroup
@@ -33,9 +34,11 @@ spec:
type: string
- jsonPath: .spec.committedResourceReservation.projectID
name: Project
+ priority: 1
type: string
- jsonPath: .spec.availabilityZone
name: AZ
+ priority: 1
type: string
- jsonPath: .spec.startTime
name: StartTime
@@ -43,6 +46,7 @@ spec:
type: string
- jsonPath: .spec.endTime
name: EndTime
+ priority: 1
type: string
- jsonPath: .spec.resources
name: Resources
@@ -163,10 +167,26 @@ spec:
description: EndTime is the time when the reservation expires.
format: date-time
type: string
+ failoverHostReservation:
+ description: |-
+ FailoverHostReservation contains fields specific to failover host reservations.
+ Only used when Type is FailoverHostReservation.
+ properties:
+ sourceHosts:
+ description: |-
+ SourceHosts is a list of hosts inside the same failover partition from
+ which a failover event towards the target host may originate.
+ See Spec.TargetHost for the target host of the failover reservation.
+ items:
+ type: string
+ type: array
+ type: object
failoverReservation:
description: |-
FailoverReservation contains fields specific to failover reservations.
Only used when Type is FailoverReservation.
+
+ Deprecated: FailoverReservations will be replaced with FailoverHostReservations.
properties:
resourceGroup:
description: ResourceGroup is the group/category of the resource
@@ -229,6 +249,7 @@ spec:
- CommittedResourceReservation
- FailoverReservation
- InFlightReservation
+ - FailoverHostReservation
type: string
required:
- type
@@ -320,10 +341,23 @@ spec:
- type
type: object
type: array
+ failoverHostReservation:
+ description: |-
+ FailoverHostReservation contains status fields specific to failover
+ host reservations. Only used when Type is FailoverHostReservation.
+ properties:
+ sourceHost:
+ description: |-
+ SourceHost is the actual host from which the failover event originates.
+ Only set when the condition ConditionTypeFailoverHostInUse is True.
+ type: string
+ type: object
failoverReservation:
description: |-
FailoverReservation contains status fields specific to failover reservations.
Only used when Type is FailoverReservation.
+
+ Deprecated: FailoverReservations will be replaced with FailoverHostReservations.
properties:
acknowledgedAt:
description: |-
diff --git a/helm/library/cortex/templates/manager/manager.yaml b/helm/library/cortex/templates/manager/manager.yaml
index 190232d8c..7e70904b6 100644
--- a/helm/library/cortex/templates/manager/manager.yaml
+++ b/helm/library/cortex/templates/manager/manager.yaml
@@ -62,17 +62,13 @@ spec:
{{- if .Values.controllerManager.container.image.pullPolicy }}
imagePullPolicy: {{ .Values.controllerManager.container.image.pullPolicy }}
{{- end }}
+ {{- if .Values.controllerManager.container.env }}
env:
- {{- if and .Values.controllerManager.container.logLevel (not (and .Values.controllerManager.container.env (hasKey .Values.controllerManager.container.env "LOG_LEVEL"))) }}
- - name: LOG_LEVEL
- value: {{ .Values.controllerManager.container.logLevel | quote }}
- {{- end }}
- {{- if .Values.controllerManager.container.env }}
{{- range $key, $value := .Values.controllerManager.container.env }}
- name: {{ $key }}
value: {{ $value }}
{{- end }}
- {{- end }}
+ {{- end }}
livenessProbe:
{{- toYaml .Values.controllerManager.container.livenessProbe | nindent 12 }}
readinessProbe:
diff --git a/helm/library/cortex/values.yaml b/helm/library/cortex/values.yaml
index 7627d86a5..d5e16b11c 100644
--- a/helm/library/cortex/values.yaml
+++ b/helm/library/cortex/values.yaml
@@ -13,8 +13,8 @@ controllerManager:
- "--metrics-bind-address=:2112"
- "--health-probe-bind-address=:8081"
- "--metrics-secure=false"
- # Log level for both zap (controller-runtime) and slog (internal packages).
- # Supported: debug, info (default), warn, error.
+ # Log level for the controller-runtime (zap) logger used across cortex.
+ # Rendered as --zap-log-level. Supported: debug, info (default), warn, error.
logLevel: "info"
# Enable zap development mode (human-readable console logs, development stack traces).
# This only changes output format and stack trace behavior, not the log level.
diff --git a/internal/knowledge/datasources/plugins/openstack/cinder/cinder_api.go b/internal/knowledge/datasources/plugins/openstack/cinder/cinder_api.go
index 6ceae0078..1abd8b365 100644
--- a/internal/knowledge/datasources/plugins/openstack/cinder/cinder_api.go
+++ b/internal/knowledge/datasources/plugins/openstack/cinder/cinder_api.go
@@ -5,7 +5,6 @@ package cinder
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources"
@@ -14,8 +13,11 @@ import (
"github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/schedulerstats"
"github.com/gophercloud/gophercloud/v2/pagination"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("cinder")
+
type CinderAPI interface {
// Int the cinder API.
Init(ctx context.Context) error
@@ -55,7 +57,7 @@ func (api *cinderAPI) Init(ctx context.Context) error {
if err != nil {
return err
}
- slog.Info("using cinder endpoint", "url", url)
+ log.Info("using cinder endpoint", "url", url)
api.sc = &gophercloud.ServiceClient{
ProviderClient: provider,
Endpoint: url,
@@ -67,7 +69,7 @@ func (api *cinderAPI) Init(ctx context.Context) error {
func (api *cinderAPI) GetAllStoragePools(ctx context.Context) ([]StoragePool, error) {
label := StoragePool{}.TableName()
- slog.Info("fetching cinder data", "label", label)
+ log.V(1).Info("fetching cinder data", "label", label)
// Fetch all pages.
pages, err := func() (pagination.Page, error) {
if api.mon.RequestTimer != nil {
@@ -87,6 +89,6 @@ func (api *cinderAPI) GetAllStoragePools(ctx context.Context) ([]StoragePool, er
if err := pages.(schedulerstats.StoragePoolPage).ExtractInto(data); err != nil {
return nil, err
}
- slog.Info("fetched", "label", label, "count", len(data.Pools))
+ log.V(1).Info("fetched", "label", label, "count", len(data.Pools))
return data.Pools, nil
}
diff --git a/internal/knowledge/datasources/plugins/openstack/controller.go b/internal/knowledge/datasources/plugins/openstack/controller.go
index 07277ce79..c16d8e1e9 100644
--- a/internal/knowledge/datasources/plugins/openstack/controller.go
+++ b/internal/knowledge/datasources/plugins/openstack/controller.go
@@ -74,14 +74,13 @@ type OpenStackDatasourceReconciler struct {
// move the current state of the cluster closer to the desired state.
func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := logf.FromContext(ctx)
- log.Info("Reconciling resource")
datasource := &v1alpha1.Datasource{}
if err := r.Get(ctx, req.NamespacedName, datasource); err != nil {
if apierrors.IsNotFound(err) {
// If the custom resource is not found then it usually means
// that it was deleted or not created.
- log.Info("Resource not found. Ignoring since object must be deleted")
+ log.V(1).Info("Resource not found. Ignoring since object must be deleted")
return ctrl.Result{}, nil
}
@@ -92,19 +91,19 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
// Sanity checks.
if datasource.Spec.Type != v1alpha1.DatasourceTypeOpenStack {
- log.Info("skipping datasource, not an openstack datasource", "name", datasource.Name)
+ log.V(1).Info("skipping datasource, not an openstack datasource", "name", datasource.Name)
return ctrl.Result{}, nil
}
if datasource.Status.NextSyncTime.After(time.Now()) && datasource.Status.NumberOfObjects != nil {
if _, seen := r.reconciledOnce.Load(req.NamespacedName); seen {
- log.Info("skipping datasource sync, not yet time", "name", datasource.Name)
+ log.V(1).Info("skipping datasource sync, not yet time", "name", datasource.Name)
return ctrl.Result{RequeueAfter: time.Until(datasource.Status.NextSyncTime.Time)}, nil
}
- log.Info("first reconcile this process lifetime, forcing sync despite timestamp", "name", datasource.Name)
+ log.V(1).Info("first reconcile this process lifetime, forcing sync despite timestamp", "name", datasource.Name)
}
// Authenticate with the database based on the secret provided in the datasource.
- log.Info("Connecting to database")
+ log.V(1).Info("Connecting to database")
authenticatedDB, err := db.Connector{Client: r.Client}.
FromSecretRef(ctx, datasource.Spec.DatabaseSecretRef)
if err != nil {
@@ -127,7 +126,7 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
// Authenticate with the datasource host if SSO is configured.
var authenticatedHTTP = http.DefaultClient
if datasource.Spec.SSOSecretRef != nil {
- log.Info("Collecting SSO credentials and authenticating with datasource host if applicable")
+ log.V(1).Info("Collecting SSO credentials and authenticating with datasource host if applicable")
authenticatedHTTP, err = sso.Connector{Client: r.Client}.
FromSecretRef(ctx, *datasource.Spec.SSOSecretRef)
if err != nil {
@@ -149,7 +148,7 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
}
// Authenticate with keystone.
- log.Info("Authenticating with keystone")
+ log.V(1).Info("Authenticating with keystone")
authenticatedKeystone, err := keystone.Connector{Client: r.Client, HTTPClient: authenticatedHTTP}.
FromSecretRef(ctx, datasource.Spec.OpenStack.SecretRef)
if err != nil {
@@ -169,7 +168,7 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
return ctrl.Result{}, err
}
- log.Info("Looking for supported syncer of datasource")
+ log.V(1).Info("Looking for supported syncer of datasource")
syncer, err := getSupportedSyncer(
*datasource,
authenticatedDB,
@@ -194,7 +193,7 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
}
// Initialize the syncer before syncing.
- log.Info("Initializing syncer for datasource")
+ log.V(1).Info("Initializing syncer for datasource")
if err := syncer.Init(ctx); err != nil {
log.Error(err, "failed to init openstack datasource", "name", datasource.Name)
old := datasource.DeepCopy()
@@ -212,11 +211,11 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
return ctrl.Result{}, err
}
- log.Info("Syncing datasource")
+ log.V(1).Info("Syncing datasource")
nResults, err := syncer.Sync(ctx)
log.Info("Finished syncing datasource", "name", datasource.Name, "numberOfResults", nResults)
if errors.Is(err, v1alpha1.ErrWaitingForDependencyDatasource) {
- log.Info("datasource sync waiting for dependency datasource", "name", datasource.Name)
+ log.V(1).Info("datasource sync waiting for dependency datasource", "name", datasource.Name)
old := datasource.DeepCopy()
meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{
Type: v1alpha1.DatasourceConditionReady,
@@ -251,7 +250,7 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
}
// Update the datasource status to reflect successful sync.
- log.Info("Synced successfully. Patching datasource.", "name", datasource.Name)
+ log.V(1).Info("Synced successfully. Patching datasource.", "name", datasource.Name)
old := datasource.DeepCopy()
meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{
Type: v1alpha1.DatasourceConditionReady,
@@ -270,7 +269,7 @@ func (r *OpenStackDatasourceReconciler) Reconcile(ctx context.Context, req ctrl.
}
// Calculate the next sync time based on the configured sync interval.
- log.Info("Finished reconcile", "next", nextTime)
+ log.V(1).Info("Finished reconcile", "next", nextTime)
r.reconciledOnce.Store(req.NamespacedName, struct{}{})
return ctrl.Result{RequeueAfter: datasource.Spec.OpenStack.SyncInterval.Duration}, nil
}
diff --git a/internal/knowledge/datasources/plugins/openstack/identity/identity_api.go b/internal/knowledge/datasources/plugins/openstack/identity/identity_api.go
index 36435645e..62da2da41 100644
--- a/internal/knowledge/datasources/plugins/openstack/identity/identity_api.go
+++ b/internal/knowledge/datasources/plugins/openstack/identity/identity_api.go
@@ -5,7 +5,6 @@ package identity
import (
"context"
- "log/slog"
"strings"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -15,8 +14,11 @@ import (
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/domains"
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/projects"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("identity")
+
type IdentityAPI interface {
Init(ctx context.Context) error
// Retrieves all domains from the OpenStack identity service.
@@ -46,7 +48,7 @@ func (api *identityAPI) Init(ctx context.Context) error {
if err != nil {
return err
}
- slog.Info("using identity endpoint", "url", url)
+ log.Info("using identity endpoint", "url", url)
api.sc = &gophercloud.ServiceClient{
ProviderClient: provider,
Endpoint: url,
@@ -58,7 +60,7 @@ func (api *identityAPI) Init(ctx context.Context) error {
// Get all the domains from the OpenStack identity service.
func (api *identityAPI) GetAllDomains(ctx context.Context) ([]Domain, error) {
label := Domain{}.TableName()
- slog.Info("fetching identity data", "label", "domains")
+ log.V(1).Info("fetching identity data", "label", "domains")
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
timer := prometheus.NewTimer(hist)
@@ -75,14 +77,14 @@ func (api *identityAPI) GetAllDomains(ctx context.Context) ([]Domain, error) {
if err := allPages.(domains.DomainPage).ExtractInto(data); err != nil {
return nil, err
}
- slog.Info("fetched identity data", "label", "domains", "count", len(data.Domains))
+ log.V(1).Info("fetched identity data", "label", "domains", "count", len(data.Domains))
return data.Domains, nil
}
// Get all the projects from the OpenStack identity service.
func (api *identityAPI) GetAllProjects(ctx context.Context) ([]Project, error) {
label := Project{}.TableName()
- slog.Info("fetching identity data", "label", "projects")
+ log.V(1).Info("fetching identity data", "label", "projects")
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
timer := prometheus.NewTimer(hist)
@@ -112,6 +114,6 @@ func (api *identityAPI) GetAllProjects(ctx context.Context) ([]Project, error) {
Tags: strings.Join(p.Tags, ","),
})
}
- slog.Info("fetched identity data", "label", "projects", "count", len(data.Projects))
+ log.V(1).Info("fetched identity data", "label", "projects", "count", len(data.Projects))
return result, nil
}
diff --git a/internal/knowledge/datasources/plugins/openstack/limes/limes_api.go b/internal/knowledge/datasources/plugins/openstack/limes/limes_api.go
index ce77e9ac0..f18572788 100644
--- a/internal/knowledge/datasources/plugins/openstack/limes/limes_api.go
+++ b/internal/knowledge/datasources/plugins/openstack/limes/limes_api.go
@@ -7,7 +7,6 @@ import (
"context"
"encoding/json"
"fmt"
- "log/slog"
"net/http"
"sync"
"time"
@@ -18,8 +17,11 @@ import (
"github.com/cobaltcore-dev/cortex/pkg/keystone"
"github.com/gophercloud/gophercloud/v2"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("limes")
+
type LimesAPI interface {
// Init the limes API.
Init(ctx context.Context) error
@@ -61,7 +63,7 @@ func (api *limesAPI) Init(ctx context.Context) error {
if err != nil {
return err
}
- slog.Info("using limes endpoint", "url", url)
+ log.Info("using limes endpoint", "url", url)
api.sc = &gophercloud.ServiceClient{
ProviderClient: provider,
Endpoint: url,
@@ -74,7 +76,7 @@ func (api *limesAPI) Init(ctx context.Context) error {
// This function fetches the commitments for each project in parallel.
func (api *limesAPI) GetAllCommitments(ctx context.Context, projects []identity.Project) ([]Commitment, error) {
label := Commitment{}.TableName()
- slog.Info("fetching limes data", "label", label)
+ log.V(1).Info("fetching limes data", "label", label)
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
timer := prometheus.NewTimer(hist)
@@ -114,7 +116,7 @@ func (api *limesAPI) GetAllCommitments(ctx context.Context, projects []identity.
// Return the first error encountered, if any.
for err := range errChan {
if err != nil {
- slog.Error("failed to resolve commitments", "error", err)
+ log.Error(err, "failed to resolve commitments")
return nil, err
}
}
@@ -141,7 +143,7 @@ func (api *limesAPI) getCommitments(ctx context.Context, project identity.Projec
// This can happen if a project was deleted after we fetched the list of projects but before we fetched the commitments for the project.
// In this case, we can simply ignore the error and return an empty list of commitments for the project.
if resp.StatusCode == http.StatusNotFound {
- slog.Warn("limes returned 404 for project, skipping", "projectID", project.ID, "domainID", project.DomainID)
+ log.V(1).Info("limes returned 404 for project, skipping", "projectID", project.ID, "domainID", project.DomainID)
return []Commitment{}, nil
}
diff --git a/internal/knowledge/datasources/plugins/openstack/manila/manila_api.go b/internal/knowledge/datasources/plugins/openstack/manila/manila_api.go
index 10d36035c..be1bdd752 100644
--- a/internal/knowledge/datasources/plugins/openstack/manila/manila_api.go
+++ b/internal/knowledge/datasources/plugins/openstack/manila/manila_api.go
@@ -6,7 +6,6 @@ package manila
import (
"context"
"fmt"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources"
@@ -16,8 +15,11 @@ import (
"github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/schedulerstats"
"github.com/gophercloud/gophercloud/v2/pagination"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("manila")
+
type ManilaAPI interface {
// Init the manila API.
Init(ctx context.Context) error
@@ -68,7 +70,7 @@ func (api *manilaAPI) Init(ctx context.Context) error {
// Get all Manila storage pools.
func (api *manilaAPI) GetAllStoragePools(ctx context.Context) ([]StoragePool, error) {
label := StoragePool{}.TableName()
- slog.Info("fetching manila data", "label", label)
+ log.V(1).Info("fetching manila data", "label", label)
// Fetch all pages.
pages, err := func() (pagination.Page, error) {
if api.mon.RequestTimer != nil {
@@ -86,10 +88,10 @@ func (api *manilaAPI) GetAllStoragePools(ctx context.Context) ([]StoragePool, er
Pools []StoragePool `json:"pools"`
}{}
// Log the raw body for debugging purposes.
- slog.Info("raw response body", "body", pages.(schedulerstats.PoolPage).Body)
+ log.V(1).Info("raw response body", "body", pages.(schedulerstats.PoolPage).Body)
if err := pages.(schedulerstats.PoolPage).ExtractInto(data); err != nil {
return nil, err
}
- slog.Info("fetched", "label", label, "count", len(data.Pools))
+ log.V(1).Info("fetched", "label", label, "count", len(data.Pools))
return data.Pools, nil
}
diff --git a/internal/knowledge/datasources/plugins/openstack/nova/nova_api.go b/internal/knowledge/datasources/plugins/openstack/nova/nova_api.go
index b7e25c2ba..f2537d5b1 100644
--- a/internal/knowledge/datasources/plugins/openstack/nova/nova_api.go
+++ b/internal/knowledge/datasources/plugins/openstack/nova/nova_api.go
@@ -7,7 +7,6 @@ import (
"context"
"encoding/json"
"fmt"
- "log/slog"
"net/http"
"net/url"
"time"
@@ -22,8 +21,11 @@ import (
"github.com/gophercloud/gophercloud/v2/pagination"
"github.com/prometheus/client_golang/prometheus"
"github.com/sapcc/go-bits/liquidapi"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("nova")
+
type NovaAPI interface {
// Init the nova API.
Init(ctx context.Context) error
@@ -73,7 +75,7 @@ func (api *novaAPI) Init(ctx context.Context) error {
if err != nil {
return err
}
- slog.Info("using nova endpoint", "url", url)
+ log.Info("using nova endpoint", "url", url)
api.sc = &gophercloud.ServiceClient{
ProviderClient: provider,
Endpoint: url,
@@ -94,7 +96,7 @@ func (api *novaAPI) Init(ctx context.Context) error {
// Get all Nova servers that are NOT deleted. (Includes ERROR, SHUTOFF etc)
func (api *novaAPI) GetAllServers(ctx context.Context) ([]Server, error) {
label := Server{}.TableName()
- slog.Info("fetching nova data", "label", label)
+ log.V(1).Info("fetching nova data", "label", label)
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
@@ -135,7 +137,7 @@ func (api *novaAPI) GetAllServers(ctx context.Context) ([]Server, error) {
}
for _, s := range list.Servers {
if _, ok := seen[s.ID]; ok {
- slog.Warn("skipping duplicate server", "id", s.ID)
+ log.V(1).Info("skipping duplicate server", "id", s.ID)
continue
}
seen[s.ID] = struct{}{}
@@ -153,7 +155,7 @@ func (api *novaAPI) GetAllServers(ctx context.Context) ([]Server, error) {
// Probe OS type concurrently for KVM servers.
api.probeOSTypes(ctx, allServers)
- slog.Info("fetched", "label", label, "count", len(allServers))
+ log.V(1).Info("fetched", "label", label, "count", len(allServers))
return allServers, nil
}
@@ -161,7 +163,7 @@ func (api *novaAPI) GetAllServers(ctx context.Context) ([]Server, error) {
// The prober caches by image ID internally, so repeated images are instant.
func (api *novaAPI) probeOSTypes(ctx context.Context, allServers []Server) {
if api.osTypeProber == nil {
- slog.Info("os_type prober not initialized, skipping")
+ log.V(1).Info("os_type prober not initialized, skipping")
return
}
var probed, resolved, unknown, rootdiskMissing int
@@ -182,7 +184,7 @@ func (api *novaAPI) probeOSTypes(ctx context.Context, allServers []Server) {
allServers[i].OSType = osType
}
}
- slog.Info("probed os_type for KVM servers",
+ log.V(1).Info("probed os_type for KVM servers",
"total", len(allServers),
"kvm", probed,
"resolved", resolved,
@@ -208,7 +210,7 @@ func (api *novaAPI) probeOSType(ctx context.Context, s Server) string {
// - This means historical server data is limited to 3 weeks
func (api *novaAPI) GetDeletedServers(ctx context.Context, since time.Time) ([]DeletedServer, error) {
label := DeletedServer{}.TableName()
- slog.Info("fetching nova data", "label", label, "changedSince", since)
+ log.V(1).Info("fetching nova data", "label", label, "changedSince", since)
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
@@ -249,7 +251,7 @@ func (api *novaAPI) GetDeletedServers(ctx context.Context, since time.Time) ([]D
}
for _, s := range list.Servers {
if _, ok := seen[s.ID]; ok {
- slog.Warn("skipping duplicate deleted server", "id", s.ID)
+ log.V(1).Info("skipping duplicate deleted server", "id", s.ID)
continue
}
seen[s.ID] = struct{}{}
@@ -264,14 +266,14 @@ func (api *novaAPI) GetDeletedServers(ctx context.Context, since time.Time) ([]D
}
}
- slog.Info("fetched", "label", label, "count", len(deletedServers))
+ log.V(1).Info("fetched", "label", label, "count", len(deletedServers))
return deletedServers, nil
}
// Get all Nova hypervisors.
func (api *novaAPI) GetAllHypervisors(ctx context.Context) ([]Hypervisor, error) {
label := Hypervisor{}.TableName()
- slog.Info("fetching nova data", "label", label)
+ log.V(1).Info("fetching nova data", "label", label)
// Note: currently we need to fetch this without gophercloud.
// Gophercloud will just assume the request is a single page even when
// the response is paginated, returning only the first page.
@@ -312,7 +314,7 @@ func (api *novaAPI) GetAllHypervisors(ctx context.Context) ([]Hypervisor, error)
}
for _, h := range list.Hypervisors {
if _, ok := seen[h.ID]; ok {
- slog.Warn("skipping duplicate hypervisor", "id", h.ID)
+ log.V(1).Info("skipping duplicate hypervisor", "id", h.ID)
continue
}
seen[h.ID] = struct{}{}
@@ -326,14 +328,14 @@ func (api *novaAPI) GetAllHypervisors(ctx context.Context) ([]Hypervisor, error)
}
}
}
- slog.Info("fetched", "label", label, "count", len(hypervisors))
+ log.V(1).Info("fetched", "label", label, "count", len(hypervisors))
return hypervisors, nil
}
// Get all Nova flavors.
func (api *novaAPI) GetAllFlavors(ctx context.Context) ([]Flavor, error) {
label := Flavor{}.TableName()
- slog.Info("fetching nova data", "label", label)
+ log.V(1).Info("fetching nova data", "label", label)
// Fetch all pages.
pages, err := func() (pagination.Page, error) {
if api.mon.RequestTimer != nil {
@@ -354,7 +356,7 @@ func (api *novaAPI) GetAllFlavors(ctx context.Context) ([]Flavor, error) {
if err := pages.(flavors.FlavorPage).ExtractInto(data); err != nil {
return nil, err
}
- slog.Info("fetched", "label", label, "count", len(data.Flavors))
+ log.V(1).Info("fetched", "label", label, "count", len(data.Flavors))
return data.Flavors, nil
}
@@ -370,7 +372,7 @@ func (api *novaAPI) GetAllFlavors(ctx context.Context) ([]Flavor, error) {
// - This means historical migration data has limited retention
func (api *novaAPI) GetAllMigrations(ctx context.Context) ([]Migration, error) {
label := Migration{}.TableName()
- slog.Info("fetching nova data", "label", label)
+ log.V(1).Info("fetching nova data", "label", label)
// Note: currently we need to fetch this without gophercloud.
// See: https://github.com/gophercloud/gophercloud/pull/3244
if api.mon.RequestTimer != nil {
@@ -411,7 +413,7 @@ func (api *novaAPI) GetAllMigrations(ctx context.Context) ([]Migration, error) {
}
for _, m := range list.Migrations {
if _, ok := seen[m.ID]; ok {
- slog.Warn("skipping duplicate migration", "id", m.ID)
+ log.V(1).Info("skipping duplicate migration", "id", m.ID)
continue
}
seen[m.ID] = struct{}{}
@@ -425,13 +427,13 @@ func (api *novaAPI) GetAllMigrations(ctx context.Context) ([]Migration, error) {
}
}
}
- slog.Info("fetched", "label", label, "count", len(migrations))
+ log.V(1).Info("fetched", "label", label, "count", len(migrations))
return migrations, nil
}
func (api *novaAPI) GetAllAggregates(ctx context.Context) ([]Aggregate, error) {
label := Aggregate{}.TableName()
- slog.Info("fetching nova data", "label", label)
+ log.V(1).Info("fetching nova data", "label", label)
pages, err := func() (pagination.Page, error) {
if api.mon.RequestTimer != nil {
@@ -455,7 +457,7 @@ func (api *novaAPI) GetAllAggregates(ctx context.Context) ([]Aggregate, error) {
return nil, err
}
- slog.Info("fetched", "label", label, "count", len(data.Aggregate))
+ log.V(1).Info("fetched", "label", label, "count", len(data.Aggregate))
aggregates := []Aggregate{}
@@ -463,9 +465,10 @@ func (api *novaAPI) GetAllAggregates(ctx context.Context) ([]Aggregate, error) {
for _, rawAggregate := range data.Aggregate {
properties, err := json.Marshal(rawAggregate.Metadata)
if err != nil {
- slog.Warn(
+ log.Error(
+ err,
"failed to marshal aggregate properties",
- "aggregate", rawAggregate.UUID, "error", err,
+ "aggregate", rawAggregate.UUID,
)
properties = []byte{}
}
@@ -497,13 +500,13 @@ func (api *novaAPI) GetAllAggregates(ctx context.Context) ([]Aggregate, error) {
func initOSTypeProber(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (prober *liquidapi.OSTypeProber) {
defer func() {
if r := recover(); r != nil {
- slog.Warn("panic during OS type prober initialization - os_type will be empty", "panic", r)
+ log.Info("panic during OS type prober initialization - os_type will be empty", "panic", r)
prober = nil
}
}()
p, err := liquidapi.NewOSTypeProber(provider, eo)
if err != nil {
- slog.Warn("failed to initialize OS type prober - os_type will be empty", "error", err)
+ log.Error(err, "failed to initialize OS type prober - os_type will be empty")
return nil
}
return p
diff --git a/internal/knowledge/datasources/plugins/openstack/nova/nova_types.go b/internal/knowledge/datasources/plugins/openstack/nova/nova_types.go
index 544b74572..f33a236ef 100644
--- a/internal/knowledge/datasources/plugins/openstack/nova/nova_types.go
+++ b/internal/knowledge/datasources/plugins/openstack/nova/nova_types.go
@@ -5,7 +5,6 @@ package nova
import (
"encoding/json"
- "log/slog"
)
// OpenStack server model as returned by the Nova API under /servers/detail.
@@ -168,7 +167,7 @@ func (s *Server) UnmarshalJSON(data []byte) error {
ID string `json:"id"`
}
if err := json.Unmarshal(aux.Image, &imageMap); err != nil {
- slog.Warn("failed to parse image ref from server response, leaving blank", "error", err, "serverID", s.ID)
+ log.Error(err, "failed to parse image ref from server response, leaving blank", "serverID", s.ID)
} else {
s.ImageRef = imageMap.ID
}
diff --git a/internal/knowledge/datasources/plugins/openstack/placement/placement_api.go b/internal/knowledge/datasources/plugins/openstack/placement/placement_api.go
index aa52f1dde..0d0eea61e 100644
--- a/internal/knowledge/datasources/plugins/openstack/placement/placement_api.go
+++ b/internal/knowledge/datasources/plugins/openstack/placement/placement_api.go
@@ -6,7 +6,6 @@ package placement
import (
"context"
"fmt"
- "log/slog"
"sync"
"time"
@@ -17,8 +16,11 @@ import (
"github.com/gophercloud/gophercloud/v2/openstack/placement/v1/resourceproviders"
"github.com/gophercloud/gophercloud/v2/pagination"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("placement")
+
type PlacementAPI interface {
// Init the placement API.
Init(ctx context.Context) error
@@ -62,7 +64,7 @@ func (api *placementAPI) Init(ctx context.Context) error {
if err != nil {
return err
}
- slog.Info("using placement endpoint", "url", url)
+ log.Info("using placement endpoint", "url", url)
api.sc = &gophercloud.ServiceClient{
ProviderClient: provider,
Endpoint: url,
@@ -76,7 +78,7 @@ func (api *placementAPI) Init(ctx context.Context) error {
// Fetch all resource providers from the placement API.
func (api *placementAPI) GetAllResourceProviders(ctx context.Context) ([]ResourceProvider, error) {
label := ResourceProvider{}.TableName()
- slog.Info("fetching placement data", "label", label)
+ log.V(1).Info("fetching placement data", "label", label)
// Fetch all pages.
pages, err := func() (pagination.Page, error) {
if api.mon.RequestTimer != nil {
@@ -96,7 +98,7 @@ func (api *placementAPI) GetAllResourceProviders(ctx context.Context) ([]Resourc
if err := pages.(resourceproviders.ResourceProvidersPage).ExtractInto(data); err != nil {
return nil, err
}
- slog.Info("fetched placement data", "label", label, "count", len(data.ResourceProviders))
+ log.V(1).Info("fetched placement data", "label", label, "count", len(data.ResourceProviders))
return data.ResourceProviders, nil
}
@@ -104,7 +106,7 @@ func (api *placementAPI) GetAllResourceProviders(ctx context.Context) ([]Resourc
// This function fetches the traits for each resource provider in parallel.
func (api *placementAPI) GetAllTraits(ctx context.Context, providers []ResourceProvider) ([]Trait, error) {
label := Trait{}.TableName()
- slog.Info("fetching placement data", "label", label)
+ log.V(1).Info("fetching placement data", "label", label)
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
timer := prometheus.NewTimer(hist)
@@ -144,7 +146,7 @@ func (api *placementAPI) GetAllTraits(ctx context.Context, providers []ResourceP
// Return the first error encountered, if any.
for err := range errChan {
if err != nil {
- slog.Error("failed to resolve traits", "error", err)
+ log.Error(err, "failed to resolve traits")
return nil, err
}
}
@@ -178,7 +180,7 @@ func (api *placementAPI) getTraits(ctx context.Context, provider ResourceProvide
// This function fetches the data for each resource provider in parallel.
func (api *placementAPI) GetAllInventoryUsages(ctx context.Context, providers []ResourceProvider) ([]InventoryUsage, error) {
label := InventoryUsage{}.TableName()
- slog.Info("fetching placement data", "label", label)
+ log.V(1).Info("fetching placement data", "label", label)
if api.mon.RequestTimer != nil {
hist := api.mon.RequestTimer.WithLabelValues(label)
timer := prometheus.NewTimer(hist)
@@ -218,7 +220,7 @@ func (api *placementAPI) GetAllInventoryUsages(ctx context.Context, providers []
// Return the first error encountered, if any.
for err := range errChan {
if err != nil {
- slog.Error("failed to resolve inventory usages", "error", err)
+ log.Error(err, "failed to resolve inventory usages")
return nil, err
}
}
diff --git a/internal/knowledge/datasources/plugins/prometheus/controller.go b/internal/knowledge/datasources/plugins/prometheus/controller.go
index bccc1fcf1..4983c89ba 100644
--- a/internal/knowledge/datasources/plugins/prometheus/controller.go
+++ b/internal/knowledge/datasources/plugins/prometheus/controller.go
@@ -68,20 +68,20 @@ func (r *PrometheusDatasourceReconciler) Reconcile(ctx context.Context, req ctrl
// Sanity checks.
if datasource.Spec.Type != v1alpha1.DatasourceTypePrometheus {
- log.Info("skipping datasource, not a prometheus datasource", "name", datasource.Name)
+ log.V(1).Info("skipping datasource, not a prometheus datasource", "name", datasource.Name)
return ctrl.Result{}, nil
}
if datasource.Status.NextSyncTime.After(time.Now()) && datasource.Status.NumberOfObjects != nil {
if _, seen := r.reconciledOnce.Load(req.NamespacedName); seen {
- log.Info("skipping datasource sync, not yet time", "name", datasource.Name)
+ log.V(1).Info("skipping datasource sync, not yet time", "name", datasource.Name)
return ctrl.Result{RequeueAfter: time.Until(datasource.Status.NextSyncTime.Time)}, nil
}
- log.Info("first reconcile this process lifetime, forcing sync despite timestamp", "name", datasource.Name)
+ log.V(1).Info("first reconcile this process lifetime, forcing sync despite timestamp", "name", datasource.Name)
}
newSyncerFunc, ok := supportedMetricSyncers[datasource.Spec.Prometheus.Type]
if !ok {
- log.Info("skipping datasource, unsupported metric type", "metricType", datasource.Spec.Prometheus.Type)
+ log.V(1).Info("skipping datasource, unsupported metric type", "metricType", datasource.Spec.Prometheus.Type)
old := datasource.DeepCopy()
meta.SetStatusCondition(&datasource.Status.Conditions, metav1.Condition{
Type: v1alpha1.DatasourceConditionReady,
diff --git a/internal/knowledge/datasources/plugins/prometheus/sync.go b/internal/knowledge/datasources/plugins/prometheus/sync.go
index ab0a56a70..6e52c7e8d 100644
--- a/internal/knowledge/datasources/plugins/prometheus/sync.go
+++ b/internal/knowledge/datasources/plugins/prometheus/sync.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"io"
- "log/slog"
"net/http"
"net/url"
"strconv"
@@ -21,8 +20,11 @@ import (
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
prometheusclient "github.com/prometheus/client_golang/prometheus"
"github.com/sapcc/go-bits/jobloop"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("prometheus")
+
type typedSyncer interface {
Sync(context.Context) (nResults int64, nextSync time.Time, err error)
}
@@ -112,7 +114,7 @@ func (s *syncer[M]) fetch(start, end time.Time) (*prometheusTimelineData[M], err
urlStr += "&start=" + strconv.FormatInt(start.Unix(), 10)
urlStr += "&end=" + strconv.FormatInt(end.Unix(), 10)
urlStr += "&step=" + strconv.Itoa(s.syncResolutionSeconds)
- slog.Info("fetching metrics from", "url", urlStr)
+ log.V(1).Info("fetching metrics from", "url", urlStr)
ctx := context.Background()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, http.NoBody)
@@ -146,10 +148,10 @@ func (s *syncer[M]) fetch(start, end time.Time) (*prometheusTimelineData[M], err
if len(bodyTrunc) > 100 {
bodyTrunc = bodyTrunc[:100] + "..."
}
- slog.Error(
+ log.Error(
+ err,
"failed to decode response",
"body", bodyTrunc,
- "error", err,
)
return nil, fmt.Errorf("failed to decode response: %w", err)
}
@@ -213,7 +215,7 @@ func (s *syncer[M]) getSyncWindowStart() (time.Time, error) {
if err != nil {
return time.Time{}, fmt.Errorf("failed to count rows: %w", err)
}
- slog.Debug("number of rows", "nRows", nRows, "tableName", tableName)
+ log.V(1).Info("number of rows", "nRows", nRows, "tableName", tableName)
if nRows == 0 {
// No metrics in the database yet. Start in the past.
start := time.Now().Add(-s.syncTimeRange)
@@ -234,7 +236,7 @@ func (s *syncer[M]) getSyncWindowStart() (time.Time, error) {
if latestTimestamp.IsZero() {
return time.Time{}, errors.New("latestTimestamp is zero")
}
- slog.Info("latest timestamp", "latestTimestamp", latestTimestamp)
+ log.V(1).Info("latest timestamp", "latestTimestamp", latestTimestamp)
return latestTimestamp, nil
}
@@ -251,7 +253,7 @@ func (s *syncer[M]) sync(start time.Time) {
var model M
tableName := model.TableName()
- slog.Info(
+ log.V(1).Info(
"syncing Prometheus data", "metricAlias", s.alias,
"start", start, "end", end, "tableName", tableName,
)
@@ -261,26 +263,26 @@ func (s *syncer[M]) sync(start time.Time) {
map[string]any{"name": s.alias, "timestamp": time.Now().Add(-s.syncTimeRange)},
)
if err != nil {
- slog.Error("failed to delete old metrics", "error", err)
+ log.Error(err, "failed to delete old metrics")
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
- slog.Error("failed to get rows affected", "error", err)
+ log.Error(err, "failed to get rows affected")
return
}
- slog.Info("deleted old metrics", "rows", rowsAffected)
+ log.V(1).Info("deleted old metrics", "rows", rowsAffected)
// Fetch the metrics from Prometheus.
prometheusData, err := s.fetch(start, end)
if err != nil {
- slog.Error("failed to fetch metrics", "error", err)
+ log.Error(err, "failed to fetch metrics")
return
}
if err := db.BulkInsert(s.db, *s.db, prometheusData.Metrics...); err != nil {
- slog.Error("failed to bulk insert metrics", "error", err)
+ log.Error(err, "failed to bulk insert metrics")
return
}
- slog.Info(
+ log.V(1).Info(
"synced Prometheus data", "newMetrics", len(prometheusData.Metrics),
"metricAlias", s.alias, "start", start, "end", end,
)
@@ -298,15 +300,15 @@ func (s *syncer[M]) Sync(context.Context) (nResults int64, nextSync time.Time, e
return 0, time.Time{}, err
}
- slog.Info("syncing metrics", "metricAlias", s.alias)
+ log.V(1).Info("syncing metrics", "metricAlias", s.alias)
// Sync this metric until we are caught up.
start, err := s.getSyncWindowStart()
if err != nil {
- slog.Error("failed to get sync window start", "error", err)
+ log.Error(err, "failed to get sync window start")
return 0, time.Time{}, err
}
s.sync(start)
- slog.Info("synced metrics", "metricAlias", s.alias)
+ log.Info("synced metrics", "metricAlias", s.alias)
nResults, err = s.db.SelectInt(
"SELECT COUNT(*) FROM "+model.TableName()+" WHERE name = :name",
diff --git a/internal/knowledge/db/db.go b/internal/knowledge/db/db.go
index d9ff32d7d..fcfb20802 100644
--- a/internal/knowledge/db/db.go
+++ b/internal/knowledge/db/db.go
@@ -8,7 +8,6 @@ import (
"database/sql"
"errors"
"fmt"
- "log/slog"
"net/url"
"reflect"
"strings"
@@ -22,8 +21,11 @@ import (
"go.xyrillian.de/gg/pgruntime"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("db")
+
// Wrapper around gorp.DbMap that adds some convenience functions.
type DB struct {
*gorp.DbMap
@@ -94,11 +96,11 @@ func (c Connector) FromSecretRef(ctx context.Context, ref corev1.SecretReference
dbUrlStr := dbURL.String()
// Strip the password from the URL for logging.
urlForLog := strings.ReplaceAll(dbUrlStr, strip(string(password)), "****")
- slog.Info("connecting to database", "url", urlForLog)
+ log.Info("connecting to database", "url", urlForLog)
// Check if we already have a connection for this url.
if conn, ok := connections.Load(dbUrlStr); ok {
- slog.Info("reusing existing database connection", "url", urlForLog)
+ log.Info("reusing existing database connection", "url", urlForLog)
return conn.(*DB), nil
}
@@ -120,13 +122,13 @@ func (c Connector) FromSecretRef(ctx context.Context, ref corev1.SecretReference
db.Close()
return nil, fmt.Errorf("giving up connecting to database: %w", lastErr)
}
- slog.Error("failed to connect to database, retrying...", "error", lastErr)
+ log.Error(lastErr, "failed to connect to database, retrying...")
time.Sleep(1 * time.Second)
}
db.SetMaxOpenConns(16)
dbMap := &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}
- slog.Info("database is ready")
+ log.Info("database is ready")
wrapped := &DB{DbMap: dbMap, host: string(host), databaseName: string(database)}
connections.Store(dbUrlStr, wrapped)
return wrapped, nil
@@ -148,11 +150,11 @@ func (d *DB) CreateTable(table ...*gorp.TableMap) error {
return fmt.Errorf("failed to begin transaction: %w", err)
}
for _, t := range table {
- slog.Info("creating table if exists", "table", t.TableName)
+ log.Info("creating table if exists", "table", t.TableName)
sql := t.SqlForCreate(true) // true means to add IF NOT EXISTS
if _, err := tx.Exec(sql); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
- slog.Error("failed to rollback transaction", "error", rbErr)
+ log.Error(rbErr, "failed to rollback transaction")
}
return fmt.Errorf("failed to create table %s: %w", t.TableName, err)
}
@@ -162,10 +164,10 @@ func (d *DB) CreateTable(table ...*gorp.TableMap) error {
// Adds a Model table to the database.
func (d *DB) AddTable(t Table) *gorp.TableMap {
- slog.Info("adding table", "table", t.TableName(), "model", t)
+ log.Info("adding table", "table", t.TableName(), "model", t)
tablemap := d.AddTableWithName(t, t.TableName())
for indexName, columnNames := range t.Indexes() {
- slog.Info("adding index", "index", indexName, "table", t.TableName(), "columns", columnNames)
+ log.Info("adding index", "index", indexName, "table", t.TableName(), "columns", columnNames)
tablemap.AddIndex(indexName, "Btree", columnNames)
}
return tablemap
@@ -188,13 +190,13 @@ func (d *DB) TableExists(t Table) bool {
WHERE type='table' AND name = :table_name
);`
default:
- slog.Error("unsupported database dialect")
+ log.Error(nil, "unsupported database dialect")
return false
}
var exists bool
err := d.SelectOne(&exists, query, map[string]any{"table_name": t.TableName()})
if err != nil {
- slog.Error("failed to check if table exists", "error", err)
+ log.Error(err, "failed to check if table exists")
return false
}
return exists
@@ -210,13 +212,13 @@ func ReplaceAll[T Table](db DB, objs ...T) error {
}
if _, err = tx.Exec("DELETE FROM " + tableName); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
- slog.Error("failed to rollback transaction", "error", rbErr)
+ log.Error(rbErr, "failed to rollback transaction")
}
return fmt.Errorf("failed to delete old objects from %s: %w", tableName, err)
}
if err = BulkInsert(tx, db, objs...); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
- slog.Error("failed to rollback transaction", "error", rbErr)
+ log.Error(rbErr, "failed to rollback transaction")
}
return fmt.Errorf("failed to insert new objects into %s: %w", tableName, err)
}
@@ -247,7 +249,7 @@ func BulkInsert[T Table](executor gorp.SqlExecutor, db DB, allObjs ...T) error {
objType := reflect.ValueOf(objs).Index(0).Type()
table, err := db.TableFor(objType, false)
if err != nil {
- slog.Error("failed to get table for object", "error", err)
+ log.Error(err, "failed to get table for object")
return err
}
@@ -295,9 +297,9 @@ func BulkInsert[T Table](executor gorp.SqlExecutor, db DB, allObjs ...T) error {
builder.WriteString(db.Dialect.QuerySuffix())
query := builder.String()
- slog.Debug("bulk inserting objects", "n", len(objs))
+ log.V(1).Info("bulk inserting objects", "n", len(objs))
if _, err = executor.Exec(query, params...); err != nil {
- slog.Error("failed to execute bulk insert", "error", err)
+ log.Error(err, "failed to execute bulk insert")
return err
}
}
diff --git a/internal/knowledge/db/testing/env.go b/internal/knowledge/db/testing/env.go
index eef6c6456..b8170e20b 100644
--- a/internal/knowledge/db/testing/env.go
+++ b/internal/knowledge/db/testing/env.go
@@ -5,8 +5,7 @@ package db
import (
"database/sql"
- "log"
- "log/slog"
+ stdlog "log"
"os"
"testing"
@@ -15,8 +14,11 @@ import (
_ "github.com/mattn/go-sqlite3"
"github.com/sapcc/go-api-declarations/bininfo"
"go.xyrillian.de/gg/pgruntime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("db-testing")
+
type DBEnv struct {
*gorp.DbMap
Close func()
@@ -26,7 +28,7 @@ func SetupDBEnv(t *testing.T) DBEnv {
var env DBEnv
// To run tests faster, the default is running with sqlite.
if os.Getenv("POSTGRES_CONTAINER") == "1" {
- slog.Info("Using real postgres container")
+ log.Info("Using real postgres container")
container := containers.PostgresContainer{}
container.Init(t)
target := pgruntime.ConnectionTarget{
@@ -49,7 +51,7 @@ func SetupDBEnv(t *testing.T) DBEnv {
env.DbMap = &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}
env.Close = container.Close
} else {
- slog.Info("Using sqlite")
+ log.Info("Using sqlite")
tmpDir := t.TempDir()
sqlDB, err := sql.Open("sqlite3", tmpDir+"/test.db")
if err != nil {
@@ -58,6 +60,6 @@ func SetupDBEnv(t *testing.T) DBEnv {
env.DbMap = &gorp.DbMap{Db: sqlDB, Dialect: gorp.SqliteDialect{}}
env.Close = func() {}
}
- env.TraceOn("[gorp]", log.New(os.Stdout, "cortex:", log.Lmicroseconds))
+ env.TraceOn("[gorp]", stdlog.New(os.Stdout, "cortex:", stdlog.Lmicroseconds))
return env
}
diff --git a/internal/knowledge/extractor/controller.go b/internal/knowledge/extractor/controller.go
index 35982675f..7bb87c33e 100644
--- a/internal/knowledge/extractor/controller.go
+++ b/internal/knowledge/extractor/controller.go
@@ -55,13 +55,13 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
recency := knowledge.Spec.Recency.Duration
if lastExtracted.Add(recency).After(time.Now()) {
waitFor := time.Until(lastExtracted.Add(recency))
- log.Info("skipping knowledge extraction, not yet time", "name", knowledge.Name, "waitFor", waitFor)
+ log.V(1).Info("skipping knowledge extraction, not yet time", "name", knowledge.Name, "waitFor", waitFor)
return ctrl.Result{RequeueAfter: waitFor}, nil
}
extractor, ok := supportedExtractors[knowledge.Spec.Extractor.Name]
if !ok {
- log.Info("skipping knowledge extraction, unsupported extractor", "name", knowledge.Spec.Extractor.Name)
+ log.V(1).Info("skipping knowledge extraction, unsupported extractor", "name", knowledge.Spec.Extractor.Name)
old := knowledge.DeepCopy()
meta.SetStatusCondition(&knowledge.Status.Conditions, metav1.Condition{
Type: v1alpha1.KnowledgeConditionReady,
@@ -231,7 +231,7 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
log.Error(err, "failed to patch knowledge status")
return ctrl.Result{}, err
}
- log.Info("successfully extracted knowledge", "name", knowledge.Name)
+ log.V(1).Info("successfully extracted knowledge", "name", knowledge.Name)
return ctrl.Result{}, nil
}
diff --git a/internal/knowledge/extractor/monitor.go b/internal/knowledge/extractor/monitor.go
index d74c74d2c..2bdcf5007 100644
--- a/internal/knowledge/extractor/monitor.go
+++ b/internal/knowledge/extractor/monitor.go
@@ -4,15 +4,16 @@
package extractor
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("extractor")
+
// Collection of Prometheus metrics to monitor feature extraction.
type Monitor struct {
// A histogram to measure how long each step takes to run.
@@ -95,7 +96,7 @@ func monitorFeatureExtractor[F plugins.FeatureExtractor](label string, f F, m Mo
// Run the wrapped feature extractor and measure the time it takes.
func (m FeatureExtractorMonitor[F]) Extract() ([]plugins.Feature, error) {
- slog.Info("features: extracting", "extractor", m.label)
+ log.V(1).Info("features: extracting", "extractor", m.label)
features, err := m.FeatureExtractor.Extract()
if err != nil {
diff --git a/internal/knowledge/extractor/plugins/base.go b/internal/knowledge/extractor/plugins/base.go
index 29a7085e2..a1823d60d 100644
--- a/internal/knowledge/extractor/plugins/base.go
+++ b/internal/knowledge/extractor/plugins/base.go
@@ -5,14 +5,16 @@ package plugins
import (
"errors"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
"github.com/cobaltcore-dev/cortex/pkg/conf"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("plugins")
+
// Common base for all extractors that provides some functionality
// that would otherwise be duplicated across all extractors.
type BaseExtractor[Opts any, Feature any] struct {
@@ -60,6 +62,6 @@ func (e *BaseExtractor[Opts, F]) Extracted(fs []F) ([]Feature, error) {
for i, f := range fs {
output[i] = f
}
- slog.Info("features: extracted", "count", len(output))
+ log.V(1).Info("features: extracted", "count", len(output))
return output, nil
}
diff --git a/internal/knowledge/extractor/plugins/compute/flavor_groups.go b/internal/knowledge/extractor/plugins/compute/flavor_groups.go
index 37af8b99e..19e729b8f 100644
--- a/internal/knowledge/extractor/plugins/compute/flavor_groups.go
+++ b/internal/knowledge/extractor/plugins/compute/flavor_groups.go
@@ -145,14 +145,14 @@ func (e *FlavorGroupExtractor) Extract() ([]plugins.Feature, error) {
var extraSpecs map[string]string
if row.ExtraSpecs != "" {
if err := json.Unmarshal([]byte(row.ExtraSpecs), &extraSpecs); err != nil {
- flavorGroupLog.Info("failed to parse extra_specs for flavor", "flavor", row.Name, "error", err)
+ flavorGroupLog.V(1).Info("failed to parse extra_specs for flavor", "flavor", row.Name, "error", err)
continue
}
}
hwVersion, exists := extraSpecs["quota:hw_version"]
if !exists || hwVersion == "" {
- flavorGroupLog.Info("flavor missing hw_version extra_spec", "flavor", row.Name)
+ flavorGroupLog.V(1).Info("flavor missing hw_version extra_spec", "flavor", row.Name)
continue
}
@@ -219,7 +219,7 @@ func (e *FlavorGroupExtractor) Extract() ([]plugins.Feature, error) {
ramCoreRatioMax = &maxRatio
}
- flavorGroupLog.Info("identified largest and smallest flavors",
+ flavorGroupLog.V(1).Info("identified largest and smallest flavors",
"groupName", groupName,
"largestFlavor", largest.Name,
"largestMemoryMB", largest.MemoryMB,
diff --git a/internal/knowledge/extractor/plugins/compute/vm_host_residency.go b/internal/knowledge/extractor/plugins/compute/vm_host_residency.go
index b5c3b6705..7f15c7894 100644
--- a/internal/knowledge/extractor/plugins/compute/vm_host_residency.go
+++ b/internal/knowledge/extractor/plugins/compute/vm_host_residency.go
@@ -6,14 +6,16 @@ package compute
import (
_ "embed"
"errors"
- "log/slog"
"strings"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins"
"github.com/cobaltcore-dev/cortex/internal/knowledge/math"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("compute")
+
// Feature that describes how long a vm was running on a host until it needed
// to move out, and the reason for the move (i.e., who forced it to move).
type VMHostResidencyHistogramBucket struct {
@@ -73,7 +75,7 @@ func (e *VMHostResidencyExtractor) Extract() ([]plugins.Feature, error) {
for key, hist := range hists {
labels := strings.Split(key, ",")
if len(labels) != 1 {
- slog.Warn("vm_host_residency: unexpected comma in flavor name")
+ log.V(1).Info("vm_host_residency: unexpected comma in flavor name")
continue
}
for bucket, value := range hist {
diff --git a/internal/knowledge/extractor/plugins/compute/vm_life_span.go b/internal/knowledge/extractor/plugins/compute/vm_life_span.go
index 4c7b99197..981d75f3a 100644
--- a/internal/knowledge/extractor/plugins/compute/vm_life_span.go
+++ b/internal/knowledge/extractor/plugins/compute/vm_life_span.go
@@ -6,7 +6,6 @@ package compute
import (
_ "embed"
"errors"
- "log/slog"
"strings"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins"
@@ -71,7 +70,7 @@ func extractHistogramBuckets(lifeSpansRaw []VMLifeSpanRaw, deleted bool) []VMLif
for key, hist := range hists {
labels := strings.Split(key, ",")
if len(labels) != 1 {
- slog.Warn("vm_life_span: unexpected comma in flavor name")
+ log.V(1).Info("vm_life_span: unexpected comma in flavor name")
continue
}
for bucket, value := range hist {
diff --git a/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_long_term.go b/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_long_term.go
index ff53bcb3c..ada48460a 100644
--- a/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_long_term.go
+++ b/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_long_term.go
@@ -7,7 +7,6 @@ import (
"context"
_ "embed"
"errors"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins"
@@ -73,7 +72,7 @@ func (e *VROpsHostsystemContentionLongTermExtractor) Extract() ([]plugins.Featur
for _, ur := range unresolved {
computeHost, ok := hostsystemToComputeHost[ur.Hostsystem]
if !ok {
- slog.Warn("skipping unresolved hostsystem", "hostsystem", ur.Hostsystem)
+ log.V(1).Info("skipping unresolved hostsystem", "hostsystem", ur.Hostsystem)
continue
}
avgsByComputeHost[computeHost] = append(avgsByComputeHost[computeHost], ur.AvgCPUContention)
@@ -83,7 +82,7 @@ func (e *VROpsHostsystemContentionLongTermExtractor) Extract() ([]plugins.Featur
var features []VROpsHostsystemContentionLongTerm
for computeHost, avgContentions := range avgsByComputeHost {
if len(avgContentions) == 0 {
- slog.Warn("no avg cpu contentions for compute host", "compute_host", computeHost)
+ log.V(1).Info("no avg cpu contentions for compute host", "compute_host", computeHost)
continue
}
var sumContentions float64 = 0
diff --git a/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_short_term.go b/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_short_term.go
index e9636063f..80f17e483 100644
--- a/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_short_term.go
+++ b/internal/knowledge/extractor/plugins/compute/vrops_hostsystem_contention_short_term.go
@@ -7,7 +7,6 @@ import (
"context"
_ "embed"
"errors"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins"
@@ -73,7 +72,7 @@ func (e *VROpsHostsystemContentionShortTermExtractor) Extract() ([]plugins.Featu
for _, ur := range unresolved {
computeHost, ok := hostsystemToComputeHost[ur.Hostsystem]
if !ok {
- slog.Warn("skipping unresolved hostsystem", "hostsystem", ur.Hostsystem)
+ log.V(1).Info("skipping unresolved hostsystem", "hostsystem", ur.Hostsystem)
continue
}
avgsByComputeHost[computeHost] = append(avgsByComputeHost[computeHost], ur.AvgCPUContention)
@@ -83,7 +82,7 @@ func (e *VROpsHostsystemContentionShortTermExtractor) Extract() ([]plugins.Featu
var features []VROpsHostsystemContentionShortTerm
for computeHost, avgContentions := range avgsByComputeHost {
if len(avgContentions) == 0 {
- slog.Warn("no avg cpu contentions for compute host", "compute_host", computeHost)
+ log.V(1).Info("no avg cpu contentions for compute host", "compute_host", computeHost)
continue
}
var sumContentions float64 = 0
diff --git a/internal/knowledge/extractor/trigger.go b/internal/knowledge/extractor/trigger.go
index 454f16c61..7e0176644 100644
--- a/internal/knowledge/extractor/trigger.go
+++ b/internal/knowledge/extractor/trigger.go
@@ -63,7 +63,7 @@ func (r *TriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
}
changedResource = knowledge
}
- log.Info(
+ log.V(1).Info(
"trigger: resource changed, finding dependents",
"resource", changedResource.GetName(),
"type", getResourceType(changedResource),
@@ -76,11 +76,11 @@ func (r *TriggerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{}, err
}
if len(dependentKnowledge) == 0 {
- log.Info("trigger: no dependent knowledge found")
+ log.V(1).Info("trigger: no dependent knowledge found")
return ctrl.Result{}, nil
}
- log.Info("trigger: found dependent knowledge", "count", len(dependentKnowledge))
+ log.V(1).Info("trigger: found dependent knowledge", "count", len(dependentKnowledge))
// Process each dependent knowledge resource
for _, knowledge := range dependentKnowledge {
if err := r.triggerKnowledgeReconciliation(ctx, knowledge); err != nil {
@@ -127,7 +127,7 @@ func (r *TriggerReconciler) findDependentKnowledge(ctx context.Context, changedR
}
}
if isDependentOnChanged {
- log.Info(
+ log.V(1).Info(
"trigger: found dependent knowledge",
"knowledge", knowledge.Name,
"dependsOn", changedResourceName,
@@ -150,7 +150,7 @@ func (r *TriggerReconciler) triggerKnowledgeReconciliation(ctx context.Context,
nextReconciliationTime := lastExtracted.Add(recency)
if nextReconciliationTime.Before(now) || nextReconciliationTime.Equal(now) {
// Recency threshold already reached - trigger immediate reconciliation
- log.Info(
+ log.V(1).Info(
"trigger: immediate reconciliation needed",
"knowledge", knowledge.Name,
"lastExtracted", lastExtracted,
@@ -161,7 +161,7 @@ func (r *TriggerReconciler) triggerKnowledgeReconciliation(ctx context.Context,
} else {
// Schedule reconciliation for when recency threshold will be reached
delay := time.Until(nextReconciliationTime)
- log.Info(
+ log.V(1).Info(
"trigger: scheduling future reconciliation",
"knowledge", knowledge.Name,
"delay", delay,
@@ -189,7 +189,7 @@ func (r *TriggerReconciler) enqueueKnowledgeReconciliation(ctx context.Context,
log.Error(err, "failed to patch knowledge to trigger reconciliation", "knowledge", knowledge.Name)
return err
}
- log.Info(
+ log.V(1).Info(
"trigger: enqueued knowledge for reconciliation",
"knowledge", knowledge.Name,
"delay", delay,
diff --git a/internal/knowledge/kpis/controller.go b/internal/knowledge/kpis/controller.go
index c35c714d9..507c26733 100644
--- a/internal/knowledge/kpis/controller.go
+++ b/internal/knowledge/kpis/controller.go
@@ -272,7 +272,7 @@ func (c *Controller) handleKPIChange(ctx context.Context, obj *v1alpha1.KPI) err
// If the dependencies are not all ready but the kpi is registered,
// unregister it.
if dependenciesReadyTotal < dependenciesTotal && registered {
- log.Info("kpi: unregistering kpi due to unready dependencies", "name", obj.Name)
+ log.V(1).Info("kpi: unregistering kpi due to unready dependencies", "name", obj.Name)
metrics.Registry.Unregister(registeredKPI)
delete(c.registeredKPIsByResourceName, obj.Name)
}
@@ -301,7 +301,7 @@ func (c *Controller) handleKPIChange(ctx context.Context, obj *v1alpha1.KPI) err
obj.Status.DependenciesReadyFrac = fmt.Sprintf("%d/%d",
dependenciesReadyTotal, dependenciesTotal)
}
- log.Info("kpi: successfully reconciled kpi", "name", obj.Name)
+ log.V(1).Info("kpi: successfully reconciled kpi", "name", obj.Name)
return nil
}
diff --git a/internal/knowledge/kpis/logger.go b/internal/knowledge/kpis/logger.go
index d6b6b025b..a4b7bed43 100644
--- a/internal/knowledge/kpis/logger.go
+++ b/internal/knowledge/kpis/logger.go
@@ -4,33 +4,34 @@
package kpis
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
"github.com/cobaltcore-dev/cortex/internal/knowledge/kpis/plugins"
"github.com/cobaltcore-dev/cortex/pkg/conf"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("kpis")
+
type kpilogger struct {
// Wrapped kpi to execute.
kpi plugins.KPI
}
func (l kpilogger) Describe(ch chan<- *prometheus.Desc) {
- slog.Info("kpi: describing", "name", l.kpi.GetName())
+ log.V(1).Info("kpi: describing", "name", l.kpi.GetName())
l.kpi.Describe(ch)
}
func (l kpilogger) Collect(ch chan<- prometheus.Metric) {
- slog.Info("kpi: collecting", "name", l.kpi.GetName())
+ log.Info("kpi: collecting", "name", l.kpi.GetName())
l.kpi.Collect(ch)
- slog.Info("kpi: collected", "name", l.kpi.GetName())
+ log.Info("kpi: collected", "name", l.kpi.GetName())
}
func (l kpilogger) Init(db *db.DB, client client.Client, opts conf.RawOpts) error {
- slog.Info("kpi: initializing", "name", l.kpi.GetName())
+ log.Info("kpi: initializing", "name", l.kpi.GetName())
return l.kpi.Init(db, client, opts)
}
diff --git a/internal/knowledge/kpis/plugins/compute/host_contention.go b/internal/knowledge/kpis/plugins/compute/host_contention.go
index 2b0aee19a..ae633bbac 100644
--- a/internal/knowledge/kpis/plugins/compute/host_contention.go
+++ b/internal/knowledge/kpis/plugins/compute/host_contention.go
@@ -5,7 +5,6 @@ package compute
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
@@ -15,8 +14,11 @@ import (
"github.com/cobaltcore-dev/cortex/pkg/conf"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("compute")
+
type VMwareHostContentionKPI struct {
// Common base for all KPIs that provides standard functionality.
plugins.BaseKPI[struct{}] // No options passed through yaml config
@@ -58,13 +60,13 @@ func (k *VMwareHostContentionKPI) Collect(ch chan<- prometheus.Metric) {
client.ObjectKey{Name: "vmware-long-term-contended-hosts"},
knowledge,
); err != nil {
- slog.Error("failed to get knowledge vmware-long-term-contended-hosts", "err", err)
+ log.Error(err, "failed to get knowledge vmware-long-term-contended-hosts")
return
}
contentions, err := v1alpha1.
UnboxFeatureList[compute.VROpsHostsystemContentionLongTerm](knowledge.Status.Raw)
if err != nil {
- slog.Error("failed to unbox vmware hostsystem contention", "err", err)
+ log.Error(err, "failed to unbox vmware hostsystem contention")
return
}
buckets := prometheus.LinearBuckets(0, 5, 20)
diff --git a/internal/knowledge/kpis/plugins/compute/project_noisiness.go b/internal/knowledge/kpis/plugins/compute/project_noisiness.go
index 290e6a8b2..d58708966 100644
--- a/internal/knowledge/kpis/plugins/compute/project_noisiness.go
+++ b/internal/knowledge/kpis/plugins/compute/project_noisiness.go
@@ -5,7 +5,6 @@ package compute
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
@@ -51,13 +50,13 @@ func (k *VMwareProjectNoisinessKPI) Collect(ch chan<- prometheus.Metric) {
client.ObjectKey{Name: "vmware-project-noisiness"},
knowledge,
); err != nil {
- slog.Error("failed to get knowledge vmware-project-noisiness", "err", err)
+ log.Error(err, "failed to get knowledge vmware-project-noisiness")
return
}
features, err := v1alpha1.
UnboxFeatureList[compute.VROpsProjectNoisiness](knowledge.Status.Raw)
if err != nil {
- slog.Error("failed to unbox vmware project noisiness", "err", err)
+ log.Error(err, "failed to unbox vmware project noisiness")
return
}
buckets := prometheus.LinearBuckets(0, 5, 20)
diff --git a/internal/knowledge/kpis/plugins/compute/vm_commitments.go b/internal/knowledge/kpis/plugins/compute/vm_commitments.go
index 15c3bb744..d84868298 100644
--- a/internal/knowledge/kpis/plugins/compute/vm_commitments.go
+++ b/internal/knowledge/kpis/plugins/compute/vm_commitments.go
@@ -5,7 +5,6 @@ package compute
import (
"fmt"
- "log/slog"
"strings"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/limes"
@@ -110,13 +109,13 @@ func (k *VMCommitmentsKPI) Collect(ch chan<- prometheus.Metric) {
SELECT * FROM `+table+`
WHERE service_type = 'compute'
`); err != nil {
- slog.Error("failed to load commitments", "err", err)
+ log.Error(err, "failed to load commitments")
return
}
var flavors []nova.Flavor
table = nova.Flavor{}.TableName()
if _, err := k.DB.Select(&flavors, "SELECT * FROM "+table); err != nil {
- slog.Error("failed to load flavors", "err", err)
+ log.Error(err, "failed to load flavors")
return
}
flavorsByName := make(map[string]nova.Flavor, len(flavors))
@@ -139,7 +138,7 @@ func (k *VMCommitmentsKPI) Collect(ch chan<- prometheus.Metric) {
// the demand, not the allocation when placed somewhere.
flavor, ok := flavorsByName[commitment.ResourceName[len("instances_"):]]
if !ok {
- slog.Warn("vm_commitments: unknown flavor in commitment", "flavor", commitment.ResourceName)
+ log.V(1).Info("vm_commitments: unknown flavor in commitment", "flavor", commitment.ResourceName)
continue
}
cores, memory := float64(flavor.VCPUs), float64(flavor.RAM)*1024*1024
@@ -156,7 +155,7 @@ func (k *VMCommitmentsKPI) Collect(ch chan<- prometheus.Metric) {
var err error
memory, err := convertLimesMemory(commitment.Unit)
if err != nil {
- slog.Warn("vm_commitments: failed to convert memory unit", "unit", commitment.Unit, "err", err)
+ log.Error(err, "vm_commitments: failed to convert memory unit", "unit", commitment.Unit)
continue
}
memory *= float64(commitment.Amount)
@@ -164,7 +163,7 @@ func (k *VMCommitmentsKPI) Collect(ch chan<- prometheus.Metric) {
statsVMCommitmentsSum[key] += memory // converted to bytes
statsCommittedMemory[key] += memory
default:
- slog.Warn("vm_commitments: unknown resource name in commitment", "resource_name", commitment.ResourceName)
+ log.V(1).Info("vm_commitments: unknown resource name in commitment", "resource_name", commitment.ResourceName)
continue
}
}
diff --git a/internal/knowledge/kpis/plugins/compute/vm_faults.go b/internal/knowledge/kpis/plugins/compute/vm_faults.go
index fec71247c..daa0a73c7 100644
--- a/internal/knowledge/kpis/plugins/compute/vm_faults.go
+++ b/internal/knowledge/kpis/plugins/compute/vm_faults.go
@@ -55,7 +55,7 @@ func (k *VMFaultsKPI) Describe(ch chan<- *prometheus.Desc) { ch <- k.vmFaultsDes
// Collect collects the current state of vms from the database and sends it as
// Prometheus metrics to the provided channel.
func (k *VMFaultsKPI) Collect(ch chan<- prometheus.Metric) {
- vmFaultsKPIlogger.Info("collecting metrics")
+ vmFaultsKPIlogger.V(1).Info("collecting metrics")
// This can happen when no datasource is provided that connects to a database.
if k.DB == nil {
@@ -71,7 +71,7 @@ func (k *VMFaultsKPI) Collect(ch chan<- prometheus.Metric) {
vmFaultsKPIlogger.Error(err, "failed to query servers from database")
return
}
- vmFaultsKPIlogger.Info("queried servers from database", "nServers", nServers)
+ vmFaultsKPIlogger.V(1).Info("queried servers from database", "nServers", nServers)
// Get all flavors from the database to map them to the vms.
var flavors []nova.Flavor
@@ -80,7 +80,7 @@ func (k *VMFaultsKPI) Collect(ch chan<- prometheus.Metric) {
vmFaultsKPIlogger.Error(err, "failed to query flavors from database")
return
}
- vmFaultsKPIlogger.Info("queried flavors from database", "nFlavors", nFlavors)
+ vmFaultsKPIlogger.V(1).Info("queried flavors from database", "nFlavors", nFlavors)
flavorsByName := make(map[string]nova.Flavor, len(flavors))
for _, flavor := range flavors {
@@ -103,7 +103,7 @@ func (k *VMFaultsKPI) Collect(ch chan<- prometheus.Metric) {
for _, server := range servers {
flavor, ok := flavorsByName[server.FlavorName]
if !ok {
- vmFaultsKPIlogger.Info("warning: flavor not found for server", "server",
+ vmFaultsKPIlogger.V(1).Info("warning: flavor not found for server", "server",
server.ID, "flavor", server.FlavorName)
continue
}
@@ -147,5 +147,5 @@ func (k *VMFaultsKPI) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(k.vmFaultsDesc, prometheus.GaugeValue, count,
key.az, key.hvtype, key.state, key.errcode, key.errmessage, key.faultyVM)
}
- vmFaultsKPIlogger.Info("collected metrics", "nMetrics", len(counts))
+ vmFaultsKPIlogger.V(1).Info("collected metrics", "nMetrics", len(counts))
}
diff --git a/internal/knowledge/kpis/plugins/compute/vm_life_span.go b/internal/knowledge/kpis/plugins/compute/vm_life_span.go
index ee7954c4d..3524140ba 100644
--- a/internal/knowledge/kpis/plugins/compute/vm_life_span.go
+++ b/internal/knowledge/kpis/plugins/compute/vm_life_span.go
@@ -5,7 +5,6 @@ package compute
import (
"context"
- "log/slog"
"strconv"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -61,13 +60,13 @@ func (k *VMLifeSpanKPI) collectVMBuckets(ch chan<- prometheus.Metric, deleted bo
client.ObjectKey{Name: "vm-life-span"},
knowledge,
); err != nil {
- slog.Error("failed to get knowledge vm-life-span", "err", err)
+ log.Error(err, "failed to get knowledge vm-life-span")
return
}
buckets, err := v1alpha1.
UnboxFeatureList[compute.VMLifeSpanHistogramBucket](knowledge.Status.Raw)
if err != nil {
- slog.Error("failed to unbox vm life span", "err", err)
+ log.Error(err, "failed to unbox vm life span")
return
}
@@ -77,7 +76,7 @@ func (k *VMLifeSpanKPI) collectVMBuckets(ch chan<- prometheus.Metric, deleted bo
continue
}
if bucket.FlavorName == "" {
- slog.Warn("vm_life_span: empty flavor name in bucket", "bucket", bucket)
+ log.V(1).Info("vm_life_span: empty flavor name in bucket", "bucket", bucket)
continue
}
bucketsByFlavor[bucket.FlavorName] = append(bucketsByFlavor[bucket.FlavorName], bucket)
@@ -85,7 +84,7 @@ func (k *VMLifeSpanKPI) collectVMBuckets(ch chan<- prometheus.Metric, deleted bo
for flavor, flavorBuckets := range bucketsByFlavor {
if len(flavorBuckets) == 0 {
- slog.Warn("vm_life_span: no buckets for flavor", "flavor", flavor)
+ log.V(1).Info("vm_life_span: no buckets for flavor", "flavor", flavor)
continue
}
diff --git a/internal/knowledge/kpis/plugins/compute/vm_migration_statistics.go b/internal/knowledge/kpis/plugins/compute/vm_migration_statistics.go
index 2ae779646..ac2e66257 100644
--- a/internal/knowledge/kpis/plugins/compute/vm_migration_statistics.go
+++ b/internal/knowledge/kpis/plugins/compute/vm_migration_statistics.go
@@ -5,7 +5,6 @@ package compute
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
@@ -53,26 +52,26 @@ func (k *VMMigrationStatisticsKPI) Collect(ch chan<- prometheus.Metric) {
client.ObjectKey{Name: "vm-host-residency"},
knowledge,
); err != nil {
- slog.Error("failed to get knowledge vm-host-residency", "err", err)
+ log.Error(err, "failed to get knowledge vm-host-residency")
return
}
buckets, err := v1alpha1.
UnboxFeatureList[compute.VMHostResidencyHistogramBucket](knowledge.Status.Raw)
if err != nil {
- slog.Error("failed to unbox vm host residency", "err", err)
+ log.Error(err, "failed to unbox vm host residency")
return
}
bucketsByFlavor := make(map[string][]compute.VMHostResidencyHistogramBucket)
for _, bucket := range buckets {
if bucket.FlavorName == "" {
- slog.Warn("vm_host_residency: empty flavor name in bucket", "bucket", bucket)
+ log.V(1).Info("vm_host_residency: empty flavor name in bucket", "bucket", bucket)
continue
}
bucketsByFlavor[bucket.FlavorName] = append(bucketsByFlavor[bucket.FlavorName], bucket)
}
for flavor, buckets := range bucketsByFlavor {
if len(buckets) == 0 {
- slog.Warn("vm_host_residency: no buckets for flavor", "flavor", flavor)
+ log.V(1).Info("vm_host_residency: no buckets for flavor", "flavor", flavor)
continue
}
var count uint64
diff --git a/internal/knowledge/kpis/plugins/deployment/datasource_state.go b/internal/knowledge/kpis/plugins/deployment/datasource_state.go
index a4b65ed79..b878f4d8c 100644
--- a/internal/knowledge/kpis/plugins/deployment/datasource_state.go
+++ b/internal/knowledge/kpis/plugins/deployment/datasource_state.go
@@ -121,5 +121,5 @@ func (k *DatasourceStateKPI) Collect(ch chan<- prometheus.Metric) {
)
}
}
- datasourceStateKPILogger.Info("Collected datasource state metrics", "count", len(datasources))
+ datasourceStateKPILogger.V(1).Info("Collected datasource state metrics", "count", len(datasources))
}
diff --git a/internal/knowledge/kpis/plugins/deployment/multicluster_object_count_kpi.go b/internal/knowledge/kpis/plugins/deployment/multicluster_object_count_kpi.go
index 77365a96d..0419f27dd 100644
--- a/internal/knowledge/kpis/plugins/deployment/multicluster_object_count_kpi.go
+++ b/internal/knowledge/kpis/plugins/deployment/multicluster_object_count_kpi.go
@@ -6,7 +6,6 @@ package deployment
import (
"context"
"fmt"
- "log/slog"
"strings"
"unicode"
@@ -17,8 +16,11 @@ import (
"github.com/prometheus/client_golang/prometheus"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("deployment")
+
// MulticlusterObjectCountKPIOpts configures which GVKs to count per cluster.
// Each entry is a "group/version/Kind" string, e.g.
// "cortex.cloud/v1alpha1/HypervisorList".
@@ -94,8 +96,8 @@ func (k *MulticlusterObjectCountKPI) Collect(ch chan<- prometheus.Metric) {
for _, d := range k.descs {
perCluster, err := k.mcl.ListMetadataPerCluster(ctx, d.gvk)
if err != nil {
- slog.Error("multicluster_object_count_kpi: failed to list object metadata",
- "gvk", d.gvk, "err", err)
+ log.Error(err, "multicluster_object_count_kpi: failed to list object metadata",
+ "gvk", d.gvk)
continue
}
for _, c := range perCluster {
diff --git a/internal/knowledge/kpis/plugins/infrastructure/kvm_hana_stacking.go b/internal/knowledge/kpis/plugins/infrastructure/kvm_hana_stacking.go
index 4db427916..480685697 100644
--- a/internal/knowledge/kpis/plugins/infrastructure/kvm_hana_stacking.go
+++ b/internal/knowledge/kpis/plugins/infrastructure/kvm_hana_stacking.go
@@ -5,7 +5,6 @@ package infrastructure
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/identity"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/nova"
@@ -15,8 +14,11 @@ import (
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("infrastructure")
+
const hanaKVMFlavorPattern = "hana_k_%"
type kvmHanaStackingRow struct {
@@ -56,20 +58,20 @@ func (k *KVMHanaStackingKPI) Describe(ch chan<- *prometheus.Desc) {
func (k *KVMHanaStackingKPI) Collect(ch chan<- prometheus.Metric) {
hosts, err := k.getKVMHosts()
if err != nil {
- slog.Error("kvm_hana_stacking: failed to get KVM hosts", "error", err)
+ log.Error(err, "kvm_hana_stacking: failed to get KVM hosts")
return
}
rows, err := k.queryHanaStacking()
if err != nil {
- slog.Error("kvm_hana_stacking: failed to query HANA stacking", "error", err)
+ log.Error(err, "kvm_hana_stacking: failed to query HANA stacking")
return
}
for _, row := range rows {
host, ok := hosts[row.ComputeHost]
if !ok {
- slog.Warn("kvm_hana_stacking: compute host not found", "compute_host", row.ComputeHost)
+ log.V(1).Info("kvm_hana_stacking: compute host not found", "compute_host", row.ComputeHost)
continue
}
hostLabels := host.getHostLabels()
diff --git a/internal/knowledge/kpis/plugins/infrastructure/kvm_host_capacity.go b/internal/knowledge/kpis/plugins/infrastructure/kvm_host_capacity.go
index 399648403..670f0bfb6 100644
--- a/internal/knowledge/kpis/plugins/infrastructure/kvm_host_capacity.go
+++ b/internal/knowledge/kpis/plugins/infrastructure/kvm_host_capacity.go
@@ -5,7 +5,6 @@ package infrastructure
import (
"context"
- "log/slog"
"k8s.io/apimachinery/pkg/api/resource"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -146,13 +145,13 @@ func (k *KVMHostCapacityKPI) getHypervisors() ([]kvmHost, error) {
func (k *KVMHostCapacityKPI) Collect(ch chan<- prometheus.Metric) {
hypervisors, err := k.getHypervisors()
if err != nil {
- slog.Error("failed to get hypervisors", "error", err)
+ log.Error(err, "failed to get hypervisors")
return
}
reservations := &v1alpha1.ReservationList{}
if err := k.Client.List(context.Background(), reservations); err != nil {
- slog.Error("failed to list reservations", "error", err)
+ log.Error(err, "failed to list reservations")
return
}
@@ -164,7 +163,7 @@ func (k *KVMHostCapacityKPI) Collect(ch chan<- prometheus.Metric) {
ramTotal, hasRAMTotal := hypervisor.getResourceCapacity(hv1.ResourceMemory)
if !hasCPUTotal || !hasRAMTotal {
- slog.Warn("hypervisor missing cpu or ram capacity, skipping", "host", hypervisor.Name)
+ log.V(1).Info("hypervisor missing cpu or ram capacity, skipping", "host", hypervisor.Name)
continue
}
diff --git a/internal/knowledge/kpis/plugins/infrastructure/kvm_project_utilization.go b/internal/knowledge/kpis/plugins/infrastructure/kvm_project_utilization.go
index f80b9bd22..605dcc03d 100644
--- a/internal/knowledge/kpis/plugins/infrastructure/kvm_project_utilization.go
+++ b/internal/knowledge/kpis/plugins/infrastructure/kvm_project_utilization.go
@@ -5,7 +5,6 @@ package infrastructure
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/identity"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/nova"
@@ -81,20 +80,20 @@ func (k *KVMProjectUtilizationKPI) Describe(ch chan<- *prometheus.Desc) {
func (k *KVMProjectUtilizationKPI) Collect(ch chan<- prometheus.Metric) {
hosts, err := k.getKVMHosts()
if err != nil {
- slog.Error("kvm_project_utilization: failed to get KVM hosts", "error", err)
+ log.Error(err, "kvm_project_utilization: failed to get KVM hosts")
return
}
// Export project x flavor x compute_host instance count metric
projectInstanceCounts, err := k.queryProjectInstanceCount()
if err != nil {
- slog.Error("kvm_project_utilization: Failed to query project instance count for project utilization KPI", "error", err)
+ log.Error(err, "kvm_project_utilization: Failed to query project instance count for project utilization KPI")
return
}
for _, projectInstanceCount := range projectInstanceCounts {
host, ok := hosts[projectInstanceCount.ComputeHost]
if !ok {
- slog.Warn("kvm_project_utilization: Compute host not found for project instance count", "compute_host", projectInstanceCount.ComputeHost)
+ log.V(1).Info("kvm_project_utilization: Compute host not found for project instance count", "compute_host", projectInstanceCount.ComputeHost)
continue
}
hostLabels := host.getHostLabels()
@@ -105,13 +104,13 @@ func (k *KVMProjectUtilizationKPI) Collect(ch chan<- prometheus.Metric) {
// Export project x compute_host x resource capacity usage metric
projectCapacityUsages, err := k.queryProjectCapacityUsage()
if err != nil {
- slog.Error("kvm_project_utilization: Failed to query project capacity usage for project utilization KPI", "error", err)
+ log.Error(err, "kvm_project_utilization: Failed to query project capacity usage for project utilization KPI")
return
}
for _, projectCapacityUsage := range projectCapacityUsages {
host, ok := hosts[projectCapacityUsage.ComputeHost]
if !ok {
- slog.Warn("kvm_project_utilization: Compute host not found for project capacity usage", "compute_host", projectCapacityUsage.ComputeHost)
+ log.V(1).Info("kvm_project_utilization: Compute host not found for project capacity usage", "compute_host", projectCapacityUsage.ComputeHost)
continue
}
hostLabels := host.getHostLabels()
diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_host_capacity.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_host_capacity.go
index 45508636f..a98437408 100644
--- a/internal/knowledge/kpis/plugins/infrastructure/vmware_host_capacity.go
+++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_host_capacity.go
@@ -5,7 +5,6 @@ package infrastructure
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
@@ -52,18 +51,18 @@ func (k *VMwareHostCapacityKPI) Describe(ch chan<- *prometheus.Desc) {
func (k *VMwareHostCapacityKPI) Collect(ch chan<- prometheus.Metric) {
hosts, err := k.getVMwareHosts()
if err != nil {
- slog.Error("vmware_host_capacity: failed to get vmware hosts", "error", err)
+ log.Error(err, "vmware_host_capacity: failed to get vmware hosts")
return
}
utilizations, err := k.getHostUtilizations()
if err != nil {
- slog.Error("vmware_host_capacity: failed to get host utilizations", "error", err)
+ log.Error(err, "vmware_host_capacity: failed to get host utilizations")
return
}
for _, host := range hosts {
util, ok := utilizations[host.ComputeHost]
if !ok {
- slog.Warn("vmware_host_capacity: missing utilization for host", "compute_host", host.ComputeHost)
+ log.V(1).Info("vmware_host_capacity: missing utilization for host", "compute_host", host.ComputeHost)
continue
}
@@ -107,7 +106,7 @@ func (k *VMwareHostCapacityKPI) getHostUtilizations() (map[string]compute.HostUt
m := make(map[string]compute.HostUtilization, len(utils))
for _, u := range utils {
if u.TotalVCPUsAllocatable == 0 || u.TotalRAMAllocatableMB == 0 || u.TotalDiskAllocatableGB == 0 {
- slog.Warn("vmware_host_capacity: skipping host with zero allocatable resources", "compute_host", u.ComputeHost)
+ log.V(1).Info("vmware_host_capacity: skipping host with zero allocatable resources", "compute_host", u.ComputeHost)
continue
}
m[u.ComputeHost] = u
diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go
index 95f5765b6..d93c8a586 100644
--- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go
+++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_commitments.go
@@ -4,7 +4,6 @@
package infrastructure
import (
- "log/slog"
"strings"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/identity"
@@ -67,13 +66,13 @@ func (k *VMwareProjectCommitmentsKPI) Collect(ch chan<- prometheus.Metric) {
flavorsByName, err := k.getFlavorsByName()
if err != nil {
- slog.Error("vmware_project_commitments: failed to load flavors", "err", err)
+ log.Error(err, "vmware_project_commitments: failed to load flavors")
return
}
projects, err := k.getProjectsWithDomains()
if err != nil {
- slog.Error("vmware_project_commitments: failed to load projects with domains", "err", err)
+ log.Error(err, "vmware_project_commitments: failed to load projects with domains")
return
}
@@ -166,12 +165,12 @@ func (k *VMwareProjectCommitmentsKPI) getRunningHanaServers() ([]nova.Server, er
func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor, projects map[string]projectWithDomain) {
commitments, err := k.getGeneralPurposeCommitments()
if err != nil {
- slog.Error("vmware_project_commitments: failed to load gp commitments", "err", err)
+ log.Error(err, "vmware_project_commitments: failed to load gp commitments")
return
}
servers, err := k.getGeneralPurposeServers()
if err != nil {
- slog.Error("vmware_project_commitments: failed to load gp servers", "err", err)
+ log.Error(err, "vmware_project_commitments: failed to load gp servers")
return
}
@@ -185,7 +184,7 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus
case "ram":
bytes, err := bytesFromUnit(float64(c.Amount), c.Unit)
if err != nil {
- slog.Warn("vmware_project_commitments: unknown ram unit", "unit", c.Unit, "err", err)
+ log.Error(err, "vmware_project_commitments: unknown ram unit", "unit", c.Unit)
continue
}
committed[gpKey{c.ProjectID, c.AvailabilityZone, "ram"}] += bytes
@@ -196,7 +195,7 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus
for _, s := range servers {
flavor, ok := flavorsByName[s.FlavorName]
if !ok {
- slog.Warn("vmware_project_commitments: gp flavor not found", "flavor", s.FlavorName)
+ log.V(1).Info("vmware_project_commitments: gp flavor not found", "flavor", s.FlavorName)
continue
}
used[gpKey{s.TenantID, s.OSEXTAvailabilityZone, "cpu"}] += float64(flavor.VCPUs)
@@ -224,12 +223,12 @@ func (k *VMwareProjectCommitmentsKPI) collectGeneralPurpose(ch chan<- prometheus
func (k *VMwareProjectCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, flavorsByName map[string]nova.Flavor, projects map[string]projectWithDomain) {
commitments, err := k.getHanaInstanceCommitments()
if err != nil {
- slog.Error("vmware_resource_commitments: failed to load hana commitments", "err", err)
+ log.Error(err, "vmware_resource_commitments: failed to load hana commitments")
return
}
servers, err := k.getRunningHanaServers()
if err != nil {
- slog.Error("vmware_resource_commitments: failed to load hana servers", "err", err)
+ log.Error(err, "vmware_resource_commitments: failed to load hana servers")
return
}
@@ -260,7 +259,7 @@ func (k *VMwareProjectCommitmentsKPI) collectHana(ch chan<- prometheus.Metric, f
unused := total - run
flavor, ok := flavorsByName[ck.flavorName]
if !ok {
- slog.Warn("vmware_resource_commitments: hana flavor not found", "flavor", ck.flavorName)
+ log.V(1).Info("vmware_resource_commitments: hana flavor not found", "flavor", ck.flavorName)
continue
}
totals[resourceKey{ck.projectID, ck.az, ck.cpuArch, "cpu"}] += float64(unused) * float64(flavor.VCPUs)
diff --git a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go
index 2459cb4c6..68cd213cf 100644
--- a/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go
+++ b/internal/knowledge/kpis/plugins/infrastructure/vmware_project_utilization.go
@@ -5,7 +5,6 @@ package infrastructure
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/datasources/plugins/openstack/identity"
@@ -83,20 +82,20 @@ func (k *VMwareProjectUtilizationKPI) Collect(ch chan<- prometheus.Metric) {
hosts, err := k.getVMwareHosts()
if err != nil {
// Log the error and return early to avoid panicking. The KPI will be retried on the next scrape.
- slog.Error("vmware_project_utilization: Failed to get VMware hosts for project utilization KPI", "error", err)
+ log.Error(err, "vmware_project_utilization: Failed to get VMware hosts for project utilization KPI")
return
}
// Export project x flavor x compute_host instance count metric
projectInstanceCounts, err := k.queryProjectInstanceCount()
if err != nil {
- slog.Error("vmware_project_utilization: Failed to query project instance count for project utilization KPI", "error", err)
+ log.Error(err, "vmware_project_utilization: Failed to query project instance count for project utilization KPI")
return
}
for _, projectInstanceCount := range projectInstanceCounts {
host, ok := hosts[projectInstanceCount.ComputeHost]
if !ok {
- slog.Warn("vmware_project_utilization: Compute host not found for project instance count", "compute_host", projectInstanceCount.ComputeHost)
+ log.V(1).Info("vmware_project_utilization: Compute host not found for project instance count", "compute_host", projectInstanceCount.ComputeHost)
continue
}
hostLabels := host.getHostLabels()
@@ -107,13 +106,13 @@ func (k *VMwareProjectUtilizationKPI) Collect(ch chan<- prometheus.Metric) {
// Export project x compute_host x resource capacity usage metric
projectCapacityUsages, err := k.queryProjectCapacityUsage()
if err != nil {
- slog.Error("vmware_project_utilization: Failed to query project capacity usage for project utilization KPI", "error", err)
+ log.Error(err, "vmware_project_utilization: Failed to query project capacity usage for project utilization KPI")
return
}
for _, projectCapacityUsage := range projectCapacityUsages {
host, ok := hosts[projectCapacityUsage.ComputeHost]
if !ok {
- slog.Warn("vmware_project_utilization: Compute host not found for project capacity usage", "compute_host", projectCapacityUsage.ComputeHost)
+ log.V(1).Info("vmware_project_utilization: Compute host not found for project capacity usage", "compute_host", projectCapacityUsage.ComputeHost)
continue
}
hostLabels := host.getHostLabels()
diff --git a/internal/knowledge/kpis/plugins/storage/storage_pool_cpu.go b/internal/knowledge/kpis/plugins/storage/storage_pool_cpu.go
index f791b17d6..9502349fa 100644
--- a/internal/knowledge/kpis/plugins/storage/storage_pool_cpu.go
+++ b/internal/knowledge/kpis/plugins/storage/storage_pool_cpu.go
@@ -5,7 +5,6 @@ package storage
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/db"
@@ -15,8 +14,11 @@ import (
"github.com/cobaltcore-dev/cortex/pkg/conf"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("storage")
+
type NetAppStoragePoolCPUUsageKPI struct {
// Common base for all KPIs that provides standard functionality.
plugins.BaseKPI[struct{}] // No options passed through yaml config
@@ -58,13 +60,13 @@ func (k *NetAppStoragePoolCPUUsageKPI) Collect(ch chan<- prometheus.Metric) {
client.ObjectKey{Name: "netapp-storage-pool-cpu-usage-manila"},
knowledge,
); err != nil {
- slog.Error("failed to get knowledge netapp-storage-pool-cpu-usage", "err", err)
+ log.Error(err, "failed to get knowledge netapp-storage-pool-cpu-usage")
return
}
usages, err := v1alpha1.
UnboxFeatureList[storage.StoragePoolCPUUsage](knowledge.Status.Raw)
if err != nil {
- slog.Error("failed to unbox storage pool cpu usage", "err", err)
+ log.Error(err, "failed to unbox storage pool cpu usage")
return
}
buckets := prometheus.LinearBuckets(0, 5, 20)
diff --git a/internal/scheduling/cinder/e2e_checks.go b/internal/scheduling/cinder/e2e_checks.go
index 6b6c6715e..4c910c67c 100644
--- a/internal/scheduling/cinder/e2e_checks.go
+++ b/internal/scheduling/cinder/e2e_checks.go
@@ -8,7 +8,6 @@ import (
"context"
"encoding/json"
"io"
- "log/slog"
"net/http"
api "github.com/cobaltcore-dev/cortex/api/external/cinder"
@@ -36,7 +35,7 @@ func checkCinderSchedulerReturnsValidHosts(
Weights: map[string]float64{},
}
apiURL := "http://localhost:8080/scheduler/cinder/external"
- slog.Info("sending request to external scheduler", "apiURL", apiURL)
+ log.V(1).Info("sending request to external scheduler", "apiURL", apiURL)
requestBody := must.Return(json.Marshal(request))
buf := bytes.NewBuffer(requestBody)
@@ -48,7 +47,7 @@ func checkCinderSchedulerReturnsValidHosts(
if respRaw.StatusCode != http.StatusOK {
// Log the response body for debugging
bodyBytes := must.Return(io.ReadAll(respRaw.Body))
- slog.Error("external scheduler API returned non-200 status code",
+ log.Error(nil, "external scheduler API returned non-200 status code",
"statusCode", respRaw.StatusCode,
"responseBody", string(bodyBytes),
)
@@ -59,5 +58,5 @@ func checkCinderSchedulerReturnsValidHosts(
if len(resp.Hosts) == 0 {
panic("no share hosts found in response")
}
- slog.Info("check successful, got share hosts", "count", len(resp.Hosts))
+ log.Info("check successful, got share hosts", "count", len(resp.Hosts))
}
diff --git a/internal/scheduling/cinder/external_scheduler_api.go b/internal/scheduling/cinder/external_scheduler_api.go
index 636cad1f2..96bea5590 100644
--- a/internal/scheduling/cinder/external_scheduler_api.go
+++ b/internal/scheduling/cinder/external_scheduler_api.go
@@ -10,20 +10,23 @@ import (
"errors"
"fmt"
"io"
- "log/slog"
"net/http"
api "github.com/cobaltcore-dev/cortex/api/external/cinder"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
scheduling "github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
+var log = logf.Log.WithName("cinder-scheduler-api")
+
type HTTPAPIDelegate interface {
// Process the decision from the API. Should create and return the updated decision.
ProcessNewDecisionFromAPI(ctx context.Context, decision *v1alpha1.Decision) error
@@ -93,7 +96,7 @@ func (httpAPI *httpAPI) CinderExternalScheduler(w http.ResponseWriter, r *http.R
// Exit early if the request method is not POST.
if r.Method != http.MethodPost {
internalErr := fmt.Errorf("invalid request method: %s", r.Method)
- c.Respond(nil, http.StatusMethodNotAllowed, internalErr, "invalid request method")
+ c.Respond(logr.Logger{}, http.StatusMethodNotAllowed, internalErr, "invalid request method")
return
}
@@ -103,7 +106,7 @@ func (httpAPI *httpAPI) CinderExternalScheduler(w http.ResponseWriter, r *http.R
// If configured, log out the complete request body.
body, err := io.ReadAll(r.Body)
if err != nil {
- c.Respond(nil, http.StatusInternalServerError, err, "failed to read request body")
+ c.Respond(logr.Logger{}, http.StatusInternalServerError, err, "failed to read request body")
return
}
raw := runtime.RawExtension{Raw: body}
@@ -112,15 +115,10 @@ func (httpAPI *httpAPI) CinderExternalScheduler(w http.ResponseWriter, r *http.R
cp := body
reader := bytes.NewReader(cp)
if err := json.NewDecoder(reader).Decode(&requestData); err != nil {
- c.Respond(nil, http.StatusBadRequest, err, "failed to decode request body")
+ c.Respond(logr.Logger{}, http.StatusBadRequest, err, "failed to decode request body")
return
}
- traceArgs := requestData.GetTraceLogArgs()
- traceArgsAny := make([]any, len(traceArgs))
- for i, a := range traceArgs {
- traceArgsAny[i] = a
- }
- logger := slog.With(traceArgsAny...)
+ logger := log.WithValues(requestData.GetTraceLogArgs()...)
logger.Info("handling POST request", "url", "/scheduler/cinder/external", "body", string(body))
if ok, reason := httpAPI.canRunScheduler(requestData); !ok {
@@ -137,7 +135,7 @@ func (httpAPI *httpAPI) CinderExternalScheduler(w http.ResponseWriter, r *http.R
c.Respond(logger, http.StatusBadRequest, err, err.Error())
return
}
- logger.Info("inferred pipeline name", "pipeline", requestData.Pipeline)
+ logger.V(1).Info("inferred pipeline name", "pipeline", requestData.Pipeline)
}
// Create the decision object in kubernetes.
diff --git a/internal/scheduling/lib/api_monitor.go b/internal/scheduling/lib/api_monitor.go
index 349ea118c..4f829bf79 100644
--- a/internal/scheduling/lib/api_monitor.go
+++ b/internal/scheduling/lib/api_monitor.go
@@ -4,11 +4,11 @@
package lib
import (
- "log/slog"
"net/http"
"strconv"
"time"
+ "github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
)
@@ -52,8 +52,9 @@ func (m *APIMonitor) Callback(w http.ResponseWriter, r *http.Request, pattern st
}
// Respond to the request with the given code and error.
-// Also log the time it took to handle the request.
-func (c MonitoredCallback) Respond(logger *slog.Logger, code int, err error, text string) {
+// Also log the time it took to handle the request. A zero-value logger falls
+// back to the package logger.
+func (c MonitoredCallback) Respond(logger logr.Logger, code int, err error, text string) {
if c.apiMonitor != nil && c.apiMonitor.ApiRequestsTimer != nil {
observer := c.apiMonitor.ApiRequestsTimer.WithLabelValues(
c.r.Method,
@@ -64,10 +65,10 @@ func (c MonitoredCallback) Respond(logger *slog.Logger, code int, err error, tex
observer.Observe(time.Since(c.t).Seconds())
}
if err != nil {
- if logger == nil {
- slog.Error("failed to handle request", "error", err)
+ if logger.GetSink() == nil {
+ pipelineLog.Error(err, "failed to handle request")
} else {
- logger.Error("failed to handle request", "error", err)
+ logger.Error(err, "failed to handle request")
}
http.Error(c.w, text, code)
return
diff --git a/internal/scheduling/lib/api_monitor_test.go b/internal/scheduling/lib/api_monitor_test.go
index 5686f5396..721d9fa96 100644
--- a/internal/scheduling/lib/api_monitor_test.go
+++ b/internal/scheduling/lib/api_monitor_test.go
@@ -10,6 +10,7 @@ import (
"testing"
"time"
+ "github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
)
@@ -146,7 +147,7 @@ func TestMonitoredCallback_Respond_WithoutError(t *testing.T) {
// Add a small delay to ensure measurable duration
time.Sleep(1 * time.Millisecond)
- callback.Respond(nil, tt.code, nil, tt.text)
+ callback.Respond(logr.Discard(), tt.code, nil, tt.text)
// When there's no error, Respond doesn't set the HTTP status code
// It only records metrics with the provided code
@@ -265,7 +266,7 @@ func TestMonitoredCallback_Respond_WithError(t *testing.T) {
// Add a small delay to ensure measurable duration
time.Sleep(1 * time.Millisecond)
- callback.Respond(nil, tt.code, tt.err, tt.text)
+ callback.Respond(logr.Discard(), tt.code, tt.err, tt.text)
// Verify HTTP response
if w.Code != tt.code {
@@ -341,14 +342,14 @@ func TestMonitoredCallback_Respond_NilMonitor(t *testing.T) {
}
// Should not panic even with nil monitor
- callback.Respond(nil, 200, nil, "")
+ callback.Respond(logr.Discard(), 200, nil, "")
// Test with nil ApiRequestsTimer
monitor := &APIMonitor{ApiRequestsTimer: nil}
callback.apiMonitor = monitor
// Should not panic even with nil timer
- callback.Respond(nil, 200, nil, "")
+ callback.Respond(logr.Discard(), 200, nil, "")
}
func TestMonitoredCallback_TimeMeasurement(t *testing.T) {
@@ -375,7 +376,7 @@ func TestMonitoredCallback_TimeMeasurement(t *testing.T) {
sleepDuration := 10 * time.Millisecond
time.Sleep(sleepDuration)
- callback.Respond(nil, 200, nil, "")
+ callback.Respond(logr.Discard(), 200, nil, "")
// Verify the metric was recorded by gathering metrics
metricFamily, err := registry.Gather()
@@ -425,7 +426,7 @@ func TestMonitoredCallback_HTTPMethods(t *testing.T) {
w := httptest.NewRecorder()
callback := monitor.Callback(w, req, "/test")
- callback.Respond(nil, 200, nil, "")
+ callback.Respond(logr.Discard(), 200, nil, "")
// Verify the method label is recorded correctly
metricFamily, err := registry.Gather()
diff --git a/internal/scheduling/lib/detector_pipeline.go b/internal/scheduling/lib/detector_pipeline.go
index bed650562..435b1ec3b 100644
--- a/internal/scheduling/lib/detector_pipeline.go
+++ b/internal/scheduling/lib/detector_pipeline.go
@@ -6,7 +6,6 @@ package lib
import (
"context"
"errors"
- "log/slog"
"slices"
"strings"
"sync"
@@ -53,7 +52,7 @@ func (p *DetectorPipeline[DetectionType]) Init(
}
p.steps[stepConf.Name] = step
p.order = append(p.order, stepConf.Name)
- slog.Info("descheduler: added step", "name", stepConf.Name)
+ pipelineLog.V(1).Info("descheduler: added step", "name", stepConf.Name)
}
return unknownDetectors, detectorErrs
}
@@ -73,20 +72,20 @@ func (p *DetectorPipeline[DetectionType]) Run() map[string][]DetectionType {
var wg sync.WaitGroup
for stepName, step := range p.steps {
wg.Go(func() {
- slog.Info("descheduler: running step")
+ pipelineLog.V(1).Info("descheduler: running step")
decisions, err := step.Run()
if errors.Is(err, ErrStepSkipped) {
- slog.Info("descheduler: step skipped")
+ pipelineLog.V(1).Info("descheduler: step skipped")
return
}
lock.Lock()
defer lock.Unlock()
if err != nil {
- slog.Error("descheduler: failed to run step", "error", err)
+ pipelineLog.Error(err, "descheduler: failed to run step")
metricErrLabel = "true"
return
}
- slog.Info("descheduler: finished step")
+ pipelineLog.V(1).Info("descheduler: finished step")
decisionsByStep[stepName] = decisions
})
}
@@ -132,7 +131,7 @@ func (p *DetectorPipeline[DetectionType]) Combine(decisionsByStep map[string][]D
}
}
if !sameHost {
- slog.Error("descheduler: conflicting hosts for combined decisions", "resource", resource, "decisions", decisions)
+ pipelineLog.Error(nil, "descheduler: conflicting hosts for combined decisions", "resource", resource, "decisions", decisions)
continue
}
var reasonBuilder strings.Builder
@@ -148,6 +147,6 @@ func (p *DetectorPipeline[DetectionType]) Combine(decisionsByStep map[string][]D
combinedDecisions = append(combinedDecisions, mergedDecision)
}
- slog.Info("descheduler: combined decisions", "combined", combinedDecisions)
+ pipelineLog.V(1).Info("descheduler: combined decisions", "combined", combinedDecisions)
return combinedDecisions
}
diff --git a/internal/scheduling/lib/filter_monitor.go b/internal/scheduling/lib/filter_monitor.go
index d0afd9282..e1c6321c6 100644
--- a/internal/scheduling/lib/filter_monitor.go
+++ b/internal/scheduling/lib/filter_monitor.go
@@ -5,9 +5,9 @@ package lib
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -43,6 +43,6 @@ func (fm *FilterMonitor[RequestType]) Validate(ctx context.Context, params v1alp
}
// Run the filter and observe its execution.
-func (fm *FilterMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
+func (fm *FilterMonitor[RequestType]) Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
return fm.monitor.RunWrapped(traceLog, request, fm.filter)
}
diff --git a/internal/scheduling/lib/filter_monitor_test.go b/internal/scheduling/lib/filter_monitor_test.go
index f709d88aa..8b8b7771b 100644
--- a/internal/scheduling/lib/filter_monitor_test.go
+++ b/internal/scheduling/lib/filter_monitor_test.go
@@ -5,10 +5,10 @@ package lib
import (
"context"
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -23,7 +23,7 @@ func TestMonitorFilter(t *testing.T) {
InitFunc: func(ctx context.Context, cl client.Client, step v1alpha1.FilterSpec) error {
return nil
},
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.5, "host2": 1.0},
}, nil
@@ -77,7 +77,7 @@ func TestFilterMonitor_Init(t *testing.T) {
func TestFilterMonitor_Run(t *testing.T) {
runCalled := false
mockFilter := &mockFilter[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
runCalled = true
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.5, "host2": 1.0},
@@ -100,7 +100,7 @@ func TestFilterMonitor_Run(t *testing.T) {
Weights: map[string]float64{"host1": 0.1, "host2": 0.2, "host3": 0.3},
}
- result, err := fm.Run(slog.Default(), request)
+ result, err := fm.Run(logr.Discard(), request)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/lib/filter_test.go b/internal/scheduling/lib/filter_test.go
index 652211163..8661cc288 100644
--- a/internal/scheduling/lib/filter_test.go
+++ b/internal/scheduling/lib/filter_test.go
@@ -5,10 +5,10 @@ package lib
import (
"context"
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
@@ -16,7 +16,7 @@ import (
type mockFilter[RequestType FilterWeigherPipelineRequest] struct {
InitFunc func(ctx context.Context, client client.Client, step v1alpha1.FilterSpec) error
ValidateFunc func(ctx context.Context, params v1alpha1.Parameters) error
- RunFunc func(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error)
+ RunFunc func(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error)
}
func (m *mockFilter[RequestType]) Init(ctx context.Context, client client.Client, step v1alpha1.FilterSpec) error {
@@ -31,7 +31,7 @@ func (m *mockFilter[RequestType]) Validate(ctx context.Context, params v1alpha1.
}
return m.ValidateFunc(ctx, params)
}
-func (m *mockFilter[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
+func (m *mockFilter[RequestType]) Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
if m.RunFunc == nil {
return &FilterWeigherPipelineStepResult{}, nil
}
diff --git a/internal/scheduling/lib/filter_validation.go b/internal/scheduling/lib/filter_validation.go
index 9ad43311d..6decaf9e0 100644
--- a/internal/scheduling/lib/filter_validation.go
+++ b/internal/scheduling/lib/filter_validation.go
@@ -6,9 +6,9 @@ package lib
import (
"context"
"errors"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -20,7 +20,7 @@ type FilterValidator[RequestType FilterWeigherPipelineRequest] struct {
// Initialize the wrapped filter with the database and options.
func (s *FilterValidator[RequestType]) Init(ctx context.Context, client client.Client, step v1alpha1.FilterSpec) error {
- slog.Info("scheduler: init validation for step", "name", step.Name)
+ pipelineLog.V(1).Info("scheduler: init validation for step", "name", step.Name)
return s.Filter.Init(ctx, client, step)
}
@@ -35,7 +35,7 @@ func validateFilter[RequestType FilterWeigherPipelineRequest](filter Filter[Requ
}
// Run the filter and validate what happens.
-func (s *FilterValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
+func (s *FilterValidator[RequestType]) Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
result, err := s.Filter.Run(traceLog, request)
if err != nil {
return nil, err
diff --git a/internal/scheduling/lib/filter_validation_test.go b/internal/scheduling/lib/filter_validation_test.go
index dc35c2f6a..e7fb8421e 100644
--- a/internal/scheduling/lib/filter_validation_test.go
+++ b/internal/scheduling/lib/filter_validation_test.go
@@ -6,10 +6,10 @@ package lib
import (
"context"
"errors"
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
@@ -146,7 +146,7 @@ func TestFilterValidator_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &mockFilter[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return tt.runResult, tt.runError
},
}
@@ -154,7 +154,7 @@ func TestFilterValidator_Run(t *testing.T) {
request := mockFilterWeigherPipelineRequest{
Hosts: tt.hosts,
}
- traceLog := slog.Default()
+ traceLog := logr.Discard()
result, err := validator.Run(traceLog, request)
diff --git a/internal/scheduling/lib/filter_weigher_pipeline.go b/internal/scheduling/lib/filter_weigher_pipeline.go
index 2c6ef9289..57ab4e321 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline.go
@@ -6,7 +6,6 @@ package lib
import (
"context"
"errors"
- "log/slog"
"maps"
"math"
"slices"
@@ -14,9 +13,13 @@ import (
"sync"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var pipelineLog = logf.Log.WithName("scheduler-pipeline")
+
type FilterWeigherPipeline[RequestType FilterWeigherPipelineRequest] interface {
// Run the scheduling pipeline with the given request.
// Call-time options are read from request.GetOptions().
@@ -62,11 +65,11 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest](
filterErrors := make(map[string]error)
unknownFilters := []string{}
for _, filterConfig := range confedFilters {
- slog.Info("scheduler: configuring filter", "name", filterConfig.Name)
- slog.Info("supported:", "filters", slices.Sorted(maps.Keys(supportedFilters)))
+ pipelineLog.V(1).Info("scheduler: configuring filter", "name", filterConfig.Name)
+ pipelineLog.V(1).Info("supported:", "filters", slices.Sorted(maps.Keys(supportedFilters)))
makeFilter, ok := supportedFilters[filterConfig.Name]
if !ok {
- slog.Error("scheduler: unsupported filter", "name", filterConfig.Name)
+ pipelineLog.Error(nil, "scheduler: unsupported filter", "name", filterConfig.Name)
unknownFilters = append(unknownFilters, filterConfig.Name)
continue
}
@@ -74,13 +77,13 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest](
filter = validateFilter(filter)
filter = monitorFilter(filter, filterConfig.Name, pipelineMonitor)
if err := filter.Init(ctx, client, filterConfig); err != nil {
- slog.Warn("scheduler: failed to initialize filter", "name", filterConfig.Name, "error", err)
+ pipelineLog.Error(err, "scheduler: failed to initialize filter", "name", filterConfig.Name)
filterErrors[filterConfig.Name] = errors.New("failed to initialize filter: " + err.Error())
continue
}
filtersByName[filterConfig.Name] = filter
filtersOrder = append(filtersOrder, filterConfig.Name)
- slog.Info("scheduler: added filter", "name", filterConfig.Name)
+ pipelineLog.V(1).Info("scheduler: added filter", "name", filterConfig.Name)
}
// Load all weighers from the configuration.
@@ -90,11 +93,11 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest](
weigherErrors := make(map[string]error)
unknownWeighers := []string{}
for _, weigherConfig := range confedWeighers {
- slog.Info("scheduler: configuring weigher", "name", weigherConfig.Name)
- slog.Info("supported:", "weighers", slices.Sorted(maps.Keys(supportedWeighers)))
+ pipelineLog.V(1).Info("scheduler: configuring weigher", "name", weigherConfig.Name)
+ pipelineLog.V(1).Info("supported:", "weighers", slices.Sorted(maps.Keys(supportedWeighers)))
makeWeigher, ok := supportedWeighers[weigherConfig.Name]
if !ok {
- slog.Error("scheduler: unsupported weigher", "name", weigherConfig.Name)
+ pipelineLog.Error(nil, "scheduler: unsupported weigher", "name", weigherConfig.Name)
unknownWeighers = append(unknownWeighers, weigherConfig.Name)
continue
}
@@ -103,7 +106,7 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest](
weigher = validateWeigher(weigher)
weigher = monitorWeigher(weigher, weigherConfig.Name, pipelineMonitor)
if err := weigher.Init(ctx, client, weigherConfig); err != nil {
- slog.Warn("scheduler: failed to initialize weigher", "name", weigherConfig.Name, "error", err)
+ pipelineLog.Error(err, "scheduler: failed to initialize weigher", "name", weigherConfig.Name)
weigherErrors[weigherConfig.Name] = errors.New("failed to initialize weigher: " + err.Error())
continue
}
@@ -114,7 +117,7 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest](
} else {
weighersMultipliers[weigherConfig.Name] = *weigherConfig.Multiplier
}
- slog.Info("scheduler: added weigher", "name", weigherConfig.Name)
+ pipelineLog.V(1).Info("scheduler: added weigher", "name", weigherConfig.Name)
}
return PipelineInitResult[FilterWeigherPipeline[RequestType]]{
@@ -137,25 +140,25 @@ func InitNewFilterWeigherPipeline[RequestType FilterWeigherPipelineRequest](
// During this process, the request is mutated to only include the
// remaining hosts.
func (p *filterWeigherPipeline[RequestType]) runFilters(
- log *slog.Logger,
+ traceLog logr.Logger,
request RequestType,
) (filteredRequest RequestType, stepResults []v1alpha1.StepResult) {
filteredRequest = request
for _, filterName := range p.filtersOrder {
filter := p.filters[filterName]
- stepLog := log.With("filter", filterName)
- stepLog.Info("scheduler: running filter")
+ stepLog := traceLog.WithValues("filter", filterName)
+ stepLog.V(1).Info("scheduler: running filter")
result, err := filter.Run(stepLog, filteredRequest)
if errors.Is(err, ErrStepSkipped) {
- stepLog.Info("scheduler: filter skipped")
+ stepLog.V(1).Info("scheduler: filter skipped")
continue
}
if err != nil {
- stepLog.Error("scheduler: failed to run filter", "error", err)
+ stepLog.Error(err, "scheduler: failed to run filter")
continue
}
- stepLog.Info("scheduler: finished filter")
+ stepLog.V(1).Info("scheduler: finished filter")
stepResults = append(stepResults, v1alpha1.StepResult{
StepName: filterName,
Activations: result.Activations,
@@ -169,7 +172,7 @@ func (p *filterWeigherPipeline[RequestType]) runFilters(
// Execute weighers and collect their activations by step name.
func (p *filterWeigherPipeline[RequestType]) runWeighers(
- log *slog.Logger,
+ traceLog logr.Logger,
filteredRequest RequestType,
) map[string]map[string]float64 {
@@ -180,18 +183,18 @@ func (p *filterWeigherPipeline[RequestType]) runWeighers(
for _, weigherName := range p.weighersOrder {
weigher := p.weighers[weigherName]
wg.Go(func() {
- stepLog := log.With("weigher", weigherName)
- stepLog.Info("scheduler: running weigher")
+ stepLog := traceLog.WithValues("weigher", weigherName)
+ stepLog.V(1).Info("scheduler: running weigher")
result, err := weigher.Run(stepLog, filteredRequest)
if errors.Is(err, ErrStepSkipped) {
- stepLog.Info("scheduler: weigher skipped")
+ stepLog.V(1).Info("scheduler: weigher skipped")
return
}
if err != nil {
- stepLog.Error("scheduler: failed to run weigher", "error", err)
+ stepLog.Error(err, "scheduler: failed to run weigher")
return
}
- stepLog.Info("scheduler: finished weigher")
+ stepLog.V(1).Info("scheduler: finished weigher")
lock.Lock()
defer lock.Unlock()
activationsByStep[weigherName] = result.Activations
@@ -218,7 +221,7 @@ func (p *filterWeigherPipeline[RequestType]) normalizeInputWeights(weights map[s
// Apply the step weights to the input weights.
func (p *filterWeigherPipeline[RequestType]) applyWeights(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
stepWeights map[string]map[string]float64,
inWeights map[string]float64,
) map[string]float64 {
@@ -240,11 +243,11 @@ func (p *filterWeigherPipeline[RequestType]) applyWeights(
// This logging will help us validate the weigher multipliers are configured
// and applied correctly, as well as debug any issues with the weighers outputs.
if multiplier == 0 {
- traceLog.Info("weigher multiplier is zero, won't have any effect",
+ traceLog.V(1).Info("weigher multiplier is zero, won't have any effect",
"weigher", weigherName, "multiplier", multiplier)
}
if multiplier < 0 {
- traceLog.Info("weigher multiplier is negative, inverting weigher behavior",
+ traceLog.V(1).Info("weigher multiplier is negative, inverting weigher behavior",
"weigher", weigherName, "multiplier", multiplier)
}
outWeights = p.Apply(outWeights, weigherActivations, multiplier)
@@ -271,15 +274,10 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1.
if err := opts.Validate(); err != nil {
return v1alpha1.DecisionResult{}, err
}
- slogArgs := request.GetTraceLogArgs()
- slogArgsAny := make([]any, 0, len(slogArgs))
- for _, arg := range slogArgs {
- slogArgsAny = append(slogArgsAny, arg)
- }
- traceLog := slog.With(slogArgsAny...)
+ traceLog := pipelineLog.WithValues(request.GetTraceLogArgs()...)
hostsIn := request.GetHosts()
- traceLog.Info("scheduler: starting pipeline", "hosts", hostsIn)
+ traceLog.V(1).Info("scheduler: starting pipeline", "hosts", hostsIn)
// Normalize the input weights so we can apply step weights meaningfully.
// Only do this if there are weighers to combine with: tanh saturates large
@@ -292,12 +290,12 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1.
} else {
inWeights = maps.Clone(request.GetWeights())
}
- traceLog.Info("scheduler: input weights", "weights", inWeights)
+ traceLog.V(1).Info("scheduler: input weights", "weights", inWeights)
// Run filters first to reduce the number of hosts.
// Any weights assigned to filtered out hosts are ignored.
filteredRequest, filterStepResults := p.runFilters(traceLog, request)
- traceLog.Info(
+ traceLog.V(1).Info(
"scheduler: finished filters",
"remainingHosts", filteredRequest.GetHosts(),
)
@@ -309,15 +307,15 @@ func (p *filterWeigherPipeline[RequestType]) Run(request RequestType) (v1alpha1.
}
stepWeights := p.runWeighers(traceLog, filteredRequest)
outWeights := p.applyWeights(traceLog, stepWeights, remainingWeights)
- traceLog.Info("scheduler: output weights", "weights", outWeights)
+ traceLog.V(1).Info("scheduler: output weights", "weights", outWeights)
hosts := p.sortHostsByWeights(outWeights)
- traceLog.Info("scheduler: sorted hosts", "hosts", hosts)
+ traceLog.V(1).Info("scheduler: sorted hosts", "hosts", hosts)
if opts.MaxCandidates > 0 && len(hosts) > opts.MaxCandidates {
trimmed := hosts[opts.MaxCandidates:]
hosts = hosts[:opts.MaxCandidates]
- traceLog.Info("scheduler: trimmed candidate list to max",
+ traceLog.V(1).Info("scheduler: trimmed candidate list to max",
"maxCandidates", opts.MaxCandidates,
"kept", hosts,
"trimmed", trimmed)
diff --git a/internal/scheduling/lib/filter_weigher_pipeline_request.go b/internal/scheduling/lib/filter_weigher_pipeline_request.go
index f66431545..2c60ba0bd 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline_request.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline_request.go
@@ -4,8 +4,6 @@
package lib
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/api/scheduling"
)
@@ -22,9 +20,10 @@ type FilterWeigherPipelineRequest interface {
Filter(includedHosts map[string]float64) FilterWeigherPipelineRequest
// Get the weights for the hosts.
GetWeights() map[string]float64
- // Get logging args to be used in the step's trace log.
- // Usually, this will be the request context including the request ID.
- GetTraceLogArgs() []slog.Attr
+ // Get logging args to be used in the step's trace log, as a flat list of
+ // alternating key/value pairs suitable for logr's WithValues. Usually this
+ // will be the request context including the request ID.
+ GetTraceLogArgs() []any
// Get the call-time options for this pipeline run.
GetOptions() scheduling.Options
}
diff --git a/internal/scheduling/lib/filter_weigher_pipeline_request_test.go b/internal/scheduling/lib/filter_weigher_pipeline_request_test.go
index 3b2b6d246..dfc2ac440 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline_request_test.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline_request_test.go
@@ -4,14 +4,12 @@
package lib
import (
- "log/slog"
-
"github.com/cobaltcore-dev/cortex/api/scheduling"
)
type mockFilterWeigherPipelineRequest struct {
WeightKeys []string
- TraceLogArgs []slog.Attr
+ TraceLogArgs []any
Hosts []string
Weights map[string]float64
Pipeline string
@@ -19,7 +17,7 @@ type mockFilterWeigherPipelineRequest struct {
}
func (m mockFilterWeigherPipelineRequest) GetWeightKeys() []string { return m.WeightKeys }
-func (m mockFilterWeigherPipelineRequest) GetTraceLogArgs() []slog.Attr { return m.TraceLogArgs }
+func (m mockFilterWeigherPipelineRequest) GetTraceLogArgs() []any { return m.TraceLogArgs }
func (m mockFilterWeigherPipelineRequest) GetHosts() []string { return m.Hosts }
func (m mockFilterWeigherPipelineRequest) GetWeights() map[string]float64 { return m.Weights }
func (m mockFilterWeigherPipelineRequest) GetPipeline() string { return m.Pipeline }
diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step.go b/internal/scheduling/lib/filter_weigher_pipeline_step.go
index 54816519c..f075d5d80 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline_step.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline_step.go
@@ -6,10 +6,10 @@ package lib
import (
"context"
"fmt"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/pkg/conf"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -32,7 +32,7 @@ type FilterWeigherPipelineStep[RequestType FilterWeigherPipelineRequest] interfa
// be used to log the step's execution.
//
// Per-call options are available via request.GetOptions().
- Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error)
+ Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error)
}
// Common base for all steps that provides some functionality
diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go
index 3fcdeebcc..423fff684 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor.go
@@ -5,7 +5,6 @@ package lib
import (
"fmt"
- "log/slog"
"maps"
"math"
"slices"
@@ -14,6 +13,7 @@ import (
"strings"
"sync"
+ "github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
)
@@ -69,7 +69,7 @@ func monitorStep[RequestType FilterWeigherPipelineRequest](stepName string, m Fi
// Run the step and observe its execution.
func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
request RequestType,
step FilterWeigherPipelineStep[RequestType],
) (*FilterWeigherPipelineStepResult, error) {
@@ -84,7 +84,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
if err != nil {
return nil, err
}
- traceLog.Info(
+ traceLog.V(1).Info(
"scheduler: finished step", "name", s.stepName,
"inWeights", inWeights, "outWeights", stepResult.Activations,
)
@@ -103,7 +103,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
WithLabelValues(s.pipelineName, host, s.stepName).
Add(weight)
if weight != 0.0 {
- traceLog.Info(
+ traceLog.V(1).Info(
"scheduler: modified host weight",
"name", s.stepName, "weight", weight,
)
@@ -116,7 +116,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
hostsOut := slices.Collect(maps.Keys(stepResult.Activations))
nHostsRemoved := len(hostsIn) - len(hostsOut)
if nHostsRemoved < 0 {
- traceLog.Info(
+ traceLog.V(1).Info(
"scheduler: removed hosts",
"name", s.stepName, "count", nHostsRemoved,
)
@@ -147,7 +147,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
WithLabelValues(s.pipelineName, s.stepName, strconv.Itoa(idx))
o.Observe(float64(originalIdx))
}
- traceLog.Info(
+ traceLog.V(1).Info(
"scheduler: reordered host",
"name", s.stepName, "host", hostsOut[idx],
"originalIdx", originalIdx, "newIdx", idx,
@@ -190,7 +190,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
}
}
after := afterBuilder.String()
- traceLog.Info(msg, "before", before, "after", after)
+ traceLog.V(1).Info(msg, "before", before, "after", after)
}
// Calculate the impact for each recorded stat.
@@ -200,9 +200,9 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
}
impact, err := impact(hostsIn, hostsOut, statData.Hosts, 5)
if err != nil {
- traceLog.Error(
+ traceLog.Error(err,
"scheduler: error calculating impact",
- "name", s.stepName, "stat", statName, "error", err,
+ "name", s.stepName, "stat", statName,
)
continue
}
@@ -211,7 +211,7 @@ func (s *FilterWeigherPipelineStepMonitor[RequestType]) RunWrapped(
WithLabelValues(s.pipelineName, s.stepName, statName, statData.Unit)
stepImpactObserver.Observe(impact)
}
- traceLog.Info(
+ traceLog.V(1).Info(
"scheduler: impact for step",
"name", s.stepName, "stat", statName,
"unit", statData.Unit, "impact", impact,
@@ -251,7 +251,7 @@ func impact(before, after []string, stats map[string]float64, topK int) (float64
subimpact := idxDisplacement * statDifference
impact += subimpact
- slog.Debug(
+ pipelineLog.V(1).Info(
"scheduler: impact calculation",
"host", host,
"oldIdx", oldIdx,
@@ -263,7 +263,7 @@ func impact(before, after []string, stats map[string]float64, topK int) (float64
"subimpact", subimpact,
)
}
- slog.Debug(
+ pipelineLog.V(1).Info(
"scheduler: total impact",
"impact", impact,
"hostsBefore", before,
diff --git a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go
index f17252d42..f785d408b 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline_step_monitor_test.go
@@ -4,11 +4,10 @@
package lib
import (
- "log/slog"
- "os"
"strings"
"testing"
+ "github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
)
@@ -32,7 +31,7 @@ func TestStepMonitorRun(t *testing.T) {
removedHostsObserver: removedHostsObserver,
}
step := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.1, "host2": 1.0, "host3": 0.0},
}, nil
@@ -42,7 +41,7 @@ func TestStepMonitorRun(t *testing.T) {
Hosts: []string{"host1", "host2", "host3"},
Weights: map[string]float64{"host1": 0.2, "host2": 0.1, "host3": 0.0},
}
- if _, err := monitor.RunWrapped(slog.Default(), request, step); err != nil {
+ if _, err := monitor.RunWrapped(logr.Discard(), request, step); err != nil {
t.Fatalf("Run() error = %v, want nil", err)
}
if len(removedHostsObserver.Observations) != 1 {
@@ -67,7 +66,7 @@ func TestStepMonitorRunEvents(t *testing.T) {
stepEventCollector: stepEventCollector,
}
step := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.0},
Events: []FilterWeigherPipelineStepEvent{
@@ -80,7 +79,7 @@ func TestStepMonitorRunEvents(t *testing.T) {
Hosts: []string{"host1"},
Weights: map[string]float64{"host1": 0.0},
}
- if _, err := monitor.RunWrapped(slog.Default(), request, step); err != nil {
+ if _, err := monitor.RunWrapped(logr.Discard(), request, step); err != nil {
t.Fatalf("Run() error = %v, want nil", err)
}
if got := len(stepEventCollector.counts); got != 1 {
@@ -101,7 +100,7 @@ func TestStepMonitorRunEventsWithDynamicLabels(t *testing.T) {
stepEventCollector: stepEventCollector,
}
step := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.0},
Events: []FilterWeigherPipelineStepEvent{
@@ -117,7 +116,7 @@ func TestStepMonitorRunEventsWithDynamicLabels(t *testing.T) {
Hosts: []string{"host1"},
Weights: map[string]float64{"host1": 0.0},
}
- if _, err := monitor.RunWrapped(slog.Default(), request, step); err != nil {
+ if _, err := monitor.RunWrapped(logr.Discard(), request, step); err != nil {
t.Fatalf("Run() error = %v, want nil", err)
}
registry := prometheus.NewRegistry()
@@ -208,9 +207,6 @@ func TestImpact(t *testing.T) {
},
}
- opts := &slog.HandlerOptions{Level: slog.LevelDebug}
- handler := slog.NewTextHandler(os.Stdout, opts)
- slog.SetDefault(slog.New(handler))
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
impactValue, err := impact(tc.before, tc.after, tc.stats, 5)
diff --git a/internal/scheduling/lib/filter_weigher_pipeline_test.go b/internal/scheduling/lib/filter_weigher_pipeline_test.go
index 63ca9fe3f..9740682b0 100644
--- a/internal/scheduling/lib/filter_weigher_pipeline_test.go
+++ b/internal/scheduling/lib/filter_weigher_pipeline_test.go
@@ -5,13 +5,13 @@ package lib
import (
"context"
- "log/slog"
"math"
"slices"
"testing"
"github.com/cobaltcore-dev/cortex/api/scheduling"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -27,7 +27,7 @@ func TestPipeline_Run(t *testing.T) {
pipeline := &filterWeigherPipeline[mockFilterWeigherPipelineRequest]{
filters: map[string]Filter[mockFilterWeigherPipelineRequest]{
"mock_filter": &mockFilter[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
// Filter out host3
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{
@@ -41,7 +41,7 @@ func TestPipeline_Run(t *testing.T) {
filtersOrder: []string{"mock_filter"},
weighers: map[string]Weigher[mockFilterWeigherPipelineRequest]{
"mock_weigher": &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
// Assign weights to hosts
activations := map[string]float64{
"host1": 0.5,
@@ -191,7 +191,7 @@ func TestPipeline_ApplyStepWeights(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result := p.applyWeights(slog.Default(), tt.stepWeights, tt.inWeights)
+ result := p.applyWeights(logr.Discard(), tt.stepWeights, tt.inWeights)
for host, weight := range tt.expectedResult {
if result[host] != weight {
t.Errorf("expected weight %f for host %s, got %f", weight, host, result[host])
@@ -245,7 +245,7 @@ func TestPipeline_SortHostsByWeights(t *testing.T) {
func TestPipeline_RunFilters(t *testing.T) {
mockStep := &mockFilter[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
// Filter out host3
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{
@@ -269,7 +269,7 @@ func TestPipeline_RunFilters(t *testing.T) {
Weights: map[string]float64{"host1": 0.0, "host2": 0.0, "host3": 0.0},
}
- req, _ := p.runFilters(slog.Default(), request)
+ req, _ := p.runFilters(logr.Discard(), request)
if len(req.Hosts) != 2 {
t.Fatalf("expected 2 step results, got %d", len(req.Hosts))
}
diff --git a/internal/scheduling/lib/pipeline_controller.go b/internal/scheduling/lib/pipeline_controller.go
index 1cb4e5e06..a38f435b5 100644
--- a/internal/scheduling/lib/pipeline_controller.go
+++ b/internal/scheduling/lib/pipeline_controller.go
@@ -71,7 +71,7 @@ func (c *BasePipelineController[PipelineType]) InitAllPipelines(ctx context.Cont
if pipelineConf.Spec.Type != c.Initializer.PipelineType() {
continue
}
- log.Info("initializing existing pipeline", "pipelineName", pipelineConf.Name)
+ log.V(1).Info("initializing existing pipeline", "pipelineName", pipelineConf.Name)
c.handlePipelineChange(ctx, &pipelineConf, nil)
}
return nil
@@ -248,7 +248,7 @@ func (c *BasePipelineController[PipelineType]) handleKnowledgeChange(
return
}
log := ctrl.LoggerFrom(ctx)
- log.Info("knowledge changed, re-evaluating all pipelines", "knowledgeName", obj.Name)
+ log.V(1).Info("knowledge changed, re-evaluating all pipelines", "knowledgeName", obj.Name)
// Find all pipelines depending on this knowledge and re-evaluate them.
var pipelines v1alpha1.PipelineList
if err := c.List(ctx, &pipelines); err != nil {
diff --git a/internal/scheduling/lib/pipeline_webhook.go b/internal/scheduling/lib/pipeline_webhook.go
index 8bc618765..d0038f893 100644
--- a/internal/scheduling/lib/pipeline_webhook.go
+++ b/internal/scheduling/lib/pipeline_webhook.go
@@ -79,7 +79,7 @@ func (w *PipelineAdmissionWebhook) validatePipeline(
return nil, nil
}
- log.Info("validating pipeline", "pipelineName", pipeline.Name, "pipelineType", pipeline.Spec.Type)
+ log.V(1).Info("validating pipeline", "pipelineName", pipeline.Name, "pipelineType", pipeline.Spec.Type)
var errMsgs []string
diff --git a/internal/scheduling/lib/weigher_monitor.go b/internal/scheduling/lib/weigher_monitor.go
index df855d067..8ff09154d 100644
--- a/internal/scheduling/lib/weigher_monitor.go
+++ b/internal/scheduling/lib/weigher_monitor.go
@@ -5,9 +5,9 @@ package lib
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -43,6 +43,6 @@ func (wm *WeigherMonitor[RequestType]) Validate(ctx context.Context, params v1al
}
// Run the weigher and observe its execution.
-func (wm *WeigherMonitor[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
+func (wm *WeigherMonitor[RequestType]) Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
return wm.monitor.RunWrapped(traceLog, request, wm.weigher)
}
diff --git a/internal/scheduling/lib/weigher_monitor_test.go b/internal/scheduling/lib/weigher_monitor_test.go
index 6f8f906e3..6c670e32f 100644
--- a/internal/scheduling/lib/weigher_monitor_test.go
+++ b/internal/scheduling/lib/weigher_monitor_test.go
@@ -5,10 +5,10 @@ package lib
import (
"context"
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -23,7 +23,7 @@ func TestMonitorWeigher(t *testing.T) {
InitFunc: func(ctx context.Context, cl client.Client, step v1alpha1.WeigherSpec) error {
return nil
},
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.5, "host2": 1.0},
}, nil
@@ -77,7 +77,7 @@ func TestWeigherMonitor_Init(t *testing.T) {
func TestWeigherMonitor_Run(t *testing.T) {
runCalled := false
mockWeigher := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
runCalled = true
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{"host1": 0.5, "host2": 1.0},
@@ -100,7 +100,7 @@ func TestWeigherMonitor_Run(t *testing.T) {
Weights: map[string]float64{"host1": 0.1, "host2": 0.2, "host3": 0.3},
}
- result, err := wm.Run(slog.Default(), request)
+ result, err := wm.Run(logr.Discard(), request)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/lib/weigher_test.go b/internal/scheduling/lib/weigher_test.go
index 4660207c4..50e7e881a 100644
--- a/internal/scheduling/lib/weigher_test.go
+++ b/internal/scheduling/lib/weigher_test.go
@@ -5,10 +5,10 @@ package lib
import (
"context"
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -19,7 +19,7 @@ import (
type mockWeigher[RequestType FilterWeigherPipelineRequest] struct {
InitFunc func(ctx context.Context, client client.Client, step v1alpha1.WeigherSpec) error
ValidateFunc func(ctx context.Context, params v1alpha1.Parameters) error
- RunFunc func(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error)
+ RunFunc func(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error)
}
func (m *mockWeigher[RequestType]) Init(ctx context.Context, client client.Client, step v1alpha1.WeigherSpec) error {
@@ -34,7 +34,7 @@ func (m *mockWeigher[RequestType]) Validate(ctx context.Context, params v1alpha1
}
return m.ValidateFunc(ctx, params)
}
-func (m *mockWeigher[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
+func (m *mockWeigher[RequestType]) Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
if m.RunFunc == nil {
return &FilterWeigherPipelineStepResult{}, nil
}
diff --git a/internal/scheduling/lib/weigher_validation.go b/internal/scheduling/lib/weigher_validation.go
index 29c313451..b16c78882 100644
--- a/internal/scheduling/lib/weigher_validation.go
+++ b/internal/scheduling/lib/weigher_validation.go
@@ -6,9 +6,9 @@ package lib
import (
"context"
"errors"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -20,7 +20,7 @@ type WeigherValidator[RequestType FilterWeigherPipelineRequest] struct {
// Initialize the wrapped weigher with the database and options.
func (s *WeigherValidator[RequestType]) Init(ctx context.Context, client client.Client, step v1alpha1.WeigherSpec) error {
- slog.Info("scheduler: init validation for step", "name", step.Name)
+ pipelineLog.V(1).Info("scheduler: init validation for step", "name", step.Name)
return s.Weigher.Init(ctx, client, step)
}
@@ -35,7 +35,7 @@ func validateWeigher[RequestType FilterWeigherPipelineRequest](weigher Weigher[R
}
// Run the weigher and validate what happens.
-func (s *WeigherValidator[RequestType]) Run(traceLog *slog.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
+func (s *WeigherValidator[RequestType]) Run(traceLog logr.Logger, request RequestType) (*FilterWeigherPipelineStepResult, error) {
// Note that for some schedulers the same host (e.g. compute host) may
// appear multiple times if there is a substruct (e.g. hypervisor hostname).
// Since cortex will only schedule on the host level and not below,
@@ -48,7 +48,7 @@ func (s *WeigherValidator[RequestType]) Run(traceLog *slog.Logger, request Reque
// nothing meaningful for the weigher to do, and running it would only
// produce a misleading "no hosts remain" safety error below.
if len(deduplicated) == 0 {
- traceLog.Info("scheduler: skipping weigher, no hosts to weigh")
+ traceLog.V(1).Info("scheduler: skipping weigher, no hosts to weigh")
return nil, ErrStepSkipped
}
result, err := s.Weigher.Run(traceLog, request)
diff --git a/internal/scheduling/lib/weigher_validation_test.go b/internal/scheduling/lib/weigher_validation_test.go
index 28dee7aec..dd19fa941 100644
--- a/internal/scheduling/lib/weigher_validation_test.go
+++ b/internal/scheduling/lib/weigher_validation_test.go
@@ -6,11 +6,11 @@ package lib
import (
"context"
"errors"
- "log/slog"
"reflect"
"testing"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
@@ -78,7 +78,7 @@ func TestWeigherValidator_Init(t *testing.T) {
func TestWeigherValidator_Run_ValidHosts(t *testing.T) {
mockStep := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{
"host1": 1.0,
@@ -96,7 +96,7 @@ func TestWeigherValidator_Run_ValidHosts(t *testing.T) {
Weigher: mockStep,
}
- result, err := validator.Run(slog.Default(), request)
+ result, err := validator.Run(logr.Discard(), request)
if err != nil {
t.Errorf("Run() error = %v, want nil", err)
}
@@ -113,7 +113,7 @@ func TestWeigherValidator_Run_ValidHosts(t *testing.T) {
func TestWeigherValidator_Run_HostNumberMismatch(t *testing.T) {
mockStep := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
return &FilterWeigherPipelineStepResult{
Activations: map[string]float64{
"host1": 1.0,
@@ -130,7 +130,7 @@ func TestWeigherValidator_Run_HostNumberMismatch(t *testing.T) {
Weigher: mockStep,
}
- result, err := validator.Run(slog.Default(), request)
+ result, err := validator.Run(logr.Discard(), request)
if err == nil {
t.Errorf("Run() error = nil, want error")
}
@@ -148,7 +148,7 @@ func TestWeigherValidator_Run_HostNumberMismatch(t *testing.T) {
func TestWeigherValidator_Run_NoHosts_Skips(t *testing.T) {
called := false
mockStep := &mockWeigher[mockFilterWeigherPipelineRequest]{
- RunFunc: func(traceLog *slog.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
+ RunFunc: func(traceLog logr.Logger, request mockFilterWeigherPipelineRequest) (*FilterWeigherPipelineStepResult, error) {
called = true
return &FilterWeigherPipelineStepResult{Activations: map[string]float64{}}, nil
},
@@ -158,7 +158,7 @@ func TestWeigherValidator_Run_NoHosts_Skips(t *testing.T) {
validator := WeigherValidator[mockFilterWeigherPipelineRequest]{Weigher: mockStep}
- result, err := validator.Run(slog.Default(), request)
+ result, err := validator.Run(logr.Discard(), request)
if !errors.Is(err, ErrStepSkipped) {
t.Errorf("Run() error = %v, want ErrStepSkipped", err)
}
diff --git a/internal/scheduling/machines/plugins/filters/filter_noop.go b/internal/scheduling/machines/plugins/filters/filter_noop.go
index da901e5c0..1f46230c5 100644
--- a/internal/scheduling/machines/plugins/filters/filter_noop.go
+++ b/internal/scheduling/machines/plugins/filters/filter_noop.go
@@ -5,11 +5,11 @@ package filters
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/external/ironcore"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -31,7 +31,7 @@ func (f *NoopFilter) Validate(ctx context.Context, params v1alpha1.Parameters) e
// not in the map are considered as filtered out.
// Provide a traceLog that contains the global request id and should
// be used to log the step's execution.
-func (NoopFilter) Run(traceLog *slog.Logger, request ironcore.MachinePipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (NoopFilter) Run(traceLog logr.Logger, request ironcore.MachinePipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
activations := make(map[string]float64, len(request.Pools))
stats := make(map[string]lib.FilterWeigherPipelineStepStatistics)
// Usually you would do some filtering here, or adjust the weights.
diff --git a/internal/scheduling/machines/plugins/filters/filter_noop_test.go b/internal/scheduling/machines/plugins/filters/filter_noop_test.go
index 2fa369a4f..1921f7227 100644
--- a/internal/scheduling/machines/plugins/filters/filter_noop_test.go
+++ b/internal/scheduling/machines/plugins/filters/filter_noop_test.go
@@ -4,11 +4,11 @@
package filters
import (
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/ironcore"
ironcorev1alpha1 "github.com/cobaltcore-dev/cortex/api/external/ironcore/v1alpha1"
+ "github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -64,7 +64,7 @@ func TestNoopFilter_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &NoopFilter{}
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Errorf("expected Run() to succeed, got error: %v", err)
diff --git a/internal/scheduling/manila/e2e_checks.go b/internal/scheduling/manila/e2e_checks.go
index a14d084c2..9bc2eecb4 100644
--- a/internal/scheduling/manila/e2e_checks.go
+++ b/internal/scheduling/manila/e2e_checks.go
@@ -8,7 +8,6 @@ import (
"context"
"encoding/json"
"io"
- "log/slog"
"net/http"
api "github.com/cobaltcore-dev/cortex/api/external/manila"
@@ -59,14 +58,14 @@ func checkManilaSchedulerReturnsValidHosts(
Availability: gophercloud.Availability(authenticatedKeystone.Availability()),
}))
sc.Microversion = "2.65"
- slog.Info("authenticated against openstack", "url", sc.Endpoint)
- slog.Info("listing share hosts")
+ log.Info("authenticated against openstack", "url", sc.Endpoint)
+ log.Info("listing share hosts")
pages := must.Return(schedulerstats.List(sc, schedulerstats.ListOpts{}).AllPages(ctx))
pools := must.Return(schedulerstats.ExtractPools(pages))
if len(pools) == 0 {
panic("no storage pools found")
}
- slog.Info("found storage pools", "count", len(pools))
+ log.Info("found storage pools", "count", len(pools))
var hosts []api.ExternalSchedulerHost
weights := make(map[string]float64)
@@ -83,7 +82,7 @@ func checkManilaSchedulerReturnsValidHosts(
Weights: weights,
}
apiURL := "http://localhost:8080/scheduler/manila/external"
- slog.Info("sending request to external scheduler", "apiURL", apiURL)
+ log.Info("sending request to external scheduler", "apiURL", apiURL)
requestBody := must.Return(json.Marshal(request))
buf := bytes.NewBuffer(requestBody)
@@ -95,7 +94,7 @@ func checkManilaSchedulerReturnsValidHosts(
if respRaw.StatusCode != http.StatusOK {
// Log the response body for debugging
bodyBytes := must.Return(io.ReadAll(respRaw.Body))
- slog.Error("external scheduler API returned non-200 status code",
+ log.Error(nil, "external scheduler API returned non-200 status code",
"statusCode", respRaw.StatusCode,
"responseBody", string(bodyBytes),
)
@@ -106,5 +105,5 @@ func checkManilaSchedulerReturnsValidHosts(
if len(resp.Hosts) == 0 {
panic("no share hosts found in response")
}
- slog.Info("check successful, got share hosts", "count", len(resp.Hosts))
+ log.Info("check successful, got share hosts", "count", len(resp.Hosts))
}
diff --git a/internal/scheduling/manila/external_scheduler_api.go b/internal/scheduling/manila/external_scheduler_api.go
index 148e69cfd..2dc18d13a 100644
--- a/internal/scheduling/manila/external_scheduler_api.go
+++ b/internal/scheduling/manila/external_scheduler_api.go
@@ -10,20 +10,23 @@ import (
"errors"
"fmt"
"io"
- "log/slog"
"net/http"
api "github.com/cobaltcore-dev/cortex/api/external/manila"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
scheduling "github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
+var log = logf.Log.WithName("manila-scheduler-api")
+
type HTTPAPIDelegate interface {
// Process the decision from the API. Should create and return the updated decision.
ProcessNewDecisionFromAPI(ctx context.Context, decision *v1alpha1.Decision) error
@@ -93,7 +96,7 @@ func (httpAPI *httpAPI) ManilaExternalScheduler(w http.ResponseWriter, r *http.R
// Exit early if the request method is not POST.
if r.Method != http.MethodPost {
internalErr := fmt.Errorf("invalid request method: %s", r.Method)
- c.Respond(nil, http.StatusMethodNotAllowed, internalErr, "invalid request method")
+ c.Respond(logr.Logger{}, http.StatusMethodNotAllowed, internalErr, "invalid request method")
return
}
@@ -103,7 +106,7 @@ func (httpAPI *httpAPI) ManilaExternalScheduler(w http.ResponseWriter, r *http.R
// If configured, log out the complete request body.
body, err := io.ReadAll(r.Body)
if err != nil {
- c.Respond(nil, http.StatusInternalServerError, err, "failed to read request body")
+ c.Respond(logr.Logger{}, http.StatusInternalServerError, err, "failed to read request body")
return
}
raw := runtime.RawExtension{Raw: body}
@@ -112,15 +115,10 @@ func (httpAPI *httpAPI) ManilaExternalScheduler(w http.ResponseWriter, r *http.R
cp := body
reader := bytes.NewReader(cp)
if err := json.NewDecoder(reader).Decode(&requestData); err != nil {
- c.Respond(nil, http.StatusBadRequest, err, "failed to decode request body")
+ c.Respond(logr.Logger{}, http.StatusBadRequest, err, "failed to decode request body")
return
}
- traceArgs := requestData.GetTraceLogArgs()
- traceArgsAny := make([]any, len(traceArgs))
- for i, a := range traceArgs {
- traceArgsAny[i] = a
- }
- logger := slog.With(traceArgsAny...)
+ logger := log.WithValues(requestData.GetTraceLogArgs()...)
logger.Info("handling POST request", "url", "/scheduler/manila/external", "body", string(body))
if ok, reason := httpAPI.canRunScheduler(requestData); !ok {
@@ -137,7 +135,7 @@ func (httpAPI *httpAPI) ManilaExternalScheduler(w http.ResponseWriter, r *http.R
c.Respond(logger, http.StatusBadRequest, err, err.Error())
return
}
- logger.Info("inferred pipeline name", "pipeline", requestData.Pipeline)
+ logger.V(1).Info("inferred pipeline name", "pipeline", requestData.Pipeline)
}
// Create the decision object in kubernetes.
diff --git a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go
index ce3e30ebe..0136ba5c0 100644
--- a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go
+++ b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing.go
@@ -6,12 +6,12 @@ package weighers
import (
"context"
"errors"
- "log/slog"
api "github.com/cobaltcore-dev/cortex/api/external/manila"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/storage"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -61,7 +61,7 @@ func (s *NetappCPUUsageBalancingStep) Init(ctx context.Context, client client.Cl
}
// Downvote hosts that are highly contended.
-func (s *NetappCPUUsageBalancingStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *NetappCPUUsageBalancingStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%")
result.Statistics["max cpu contention"] = s.PrepareStats(request, "%")
diff --git a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go
index f3e9c66ea..040548df8 100644
--- a/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go
+++ b/internal/scheduling/manila/plugins/weighers/netapp_cpu_usage_balancing_test.go
@@ -4,12 +4,12 @@
package weighers
import (
- "log/slog"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/manila"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/storage"
+ "github.com/go-logr/logr"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
@@ -164,7 +164,7 @@ func TestNetappCPUUsageBalancingStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/crs/recorder.go b/internal/scheduling/nova/crs/recorder.go
index 19d0a9bdb..99cd0d8f4 100644
--- a/internal/scheduling/nova/crs/recorder.go
+++ b/internal/scheduling/nova/crs/recorder.go
@@ -119,7 +119,7 @@ func (r *Recorder) RecordPlacement(ctx context.Context, decision *v1alpha1.Decis
for _, slot := range slotsOnTarget {
if _, exists := slot.Spec.CommittedResourceReservation.Allocations[instanceUUID]; exists {
- log.Info("CR allocation: VM UUID already in reservation, skipping",
+ log.V(1).Info("CR allocation: VM UUID already in reservation, skipping",
"instanceUUID", instanceUUID, "reservation", slot.Name)
return
}
@@ -135,7 +135,7 @@ func (r *Recorder) RecordPlacement(ctx context.Context, decision *v1alpha1.Decis
return
}
- log.Info("CR allocation: writing VM UUID into reservation",
+ log.V(1).Info("CR allocation: writing VM UUID into reservation",
"instanceUUID", instanceUUID, "reservation", slotName,
"projectID", projectID, "flavorGroup", flavorGroupName, "host", selectedHost)
@@ -166,7 +166,7 @@ func (r *Recorder) RecordPlacement(ctx context.Context, decision *v1alpha1.Decis
return
}
- log.Info("CR allocation: done", "instanceUUID", instanceUUID, "reservation", slotName)
+ log.V(1).Info("CR allocation: done", "instanceUUID", instanceUUID, "reservation", slotName)
}
// RecordNoHostFound classifies a no-host-found result and emits a log line and metric.
diff --git a/internal/scheduling/nova/deschedulings_cleanup.go b/internal/scheduling/nova/deschedulings_cleanup.go
index e80d89697..127c21675 100644
--- a/internal/scheduling/nova/deschedulings_cleanup.go
+++ b/internal/scheduling/nova/deschedulings_cleanup.go
@@ -29,7 +29,7 @@ func (s *DeschedulingsCleanupOnStartup) Start(ctx context.Context) error {
log.Error(err, "failed to list descheduling resources during startup reconciliation")
return err
}
- log.Info("processing existing resources for cleanup", "resourceCount", len(resources.Items))
+ log.V(1).Info("processing existing resources for cleanup", "resourceCount", len(resources.Items))
processed, deleted := 0, 0
for _, resource := range resources.Items {
// Use the shared cleanup logic
@@ -74,7 +74,7 @@ func (r *DeschedulingsCleanup) Reconcile(ctx context.Context, req ctrl.Request)
elapsed := time.Since(descheduling.CreationTimestamp.Time)
if elapsed < 24*time.Hour {
// Not old enough yet - requeue for later
- log.Info("descheduling not old enough yet to be cleaned up", "name", descheduling.Name, "age", elapsed.String())
+ log.V(1).Info("descheduling not old enough yet to be cleaned up", "name", descheduling.Name, "age", elapsed.String())
return ctrl.Result{RequeueAfter: 24*time.Hour - elapsed}, nil
}
@@ -84,11 +84,11 @@ func (r *DeschedulingsCleanup) Reconcile(ctx context.Context, req ctrl.Request)
log.Error(err, "failed to delete old descheduling", "name", descheduling.Name)
return ctrl.Result{}, err
}
- log.Info("descheduling was already deleted", "name", descheduling.Name)
+ log.V(1).Info("descheduling was already deleted", "name", descheduling.Name)
return ctrl.Result{}, nil
}
- log.Info("descheduling was deleted", "name", descheduling.Name)
+ log.V(1).Info("descheduling was deleted", "name", descheduling.Name)
return ctrl.Result{}, nil
}
diff --git a/internal/scheduling/nova/deschedulings_executor.go b/internal/scheduling/nova/deschedulings_executor.go
index d8b7ab88d..8d5d0b31e 100644
--- a/internal/scheduling/nova/deschedulings_executor.go
+++ b/internal/scheduling/nova/deschedulings_executor.go
@@ -52,7 +52,7 @@ func (e *DeschedulingsExecutor) Reconcile(ctx context.Context, req ctrl.Request)
// Currently we only know how to handle deschedulings for nova VMs.
if descheduling.Spec.RefType != v1alpha1.DeschedulingSpecVMReferenceNovaServerUUID {
- log.Info("skipping descheduling, unsupported refType", "refType", descheduling.Spec.RefType)
+ log.V(1).Info("skipping descheduling, unsupported refType", "refType", descheduling.Spec.RefType)
old := descheduling.DeepCopy()
meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{
Type: v1alpha1.DeschedulingConditionReady,
@@ -70,7 +70,7 @@ func (e *DeschedulingsExecutor) Reconcile(ctx context.Context, req ctrl.Request)
// Currently we only know how to handle deschedulings for nova compute hosts.
if descheduling.Spec.PrevHostType != v1alpha1.DeschedulingSpecHostTypeNovaComputeHostName {
- log.Info("skipping descheduling, unsupported prevHostType", "prevHostType", descheduling.Spec.PrevHostType)
+ log.V(1).Info("skipping descheduling, unsupported prevHostType", "prevHostType", descheduling.Spec.PrevHostType)
old := descheduling.DeepCopy()
meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{
Type: v1alpha1.DeschedulingConditionReady,
@@ -88,7 +88,7 @@ func (e *DeschedulingsExecutor) Reconcile(ctx context.Context, req ctrl.Request)
// We need a server uuid to proceed.
if descheduling.Spec.Ref == "" {
- log.Info("skipping descheduling, missing ref")
+ log.V(1).Info("skipping descheduling, missing ref")
old := descheduling.DeepCopy()
meta.SetStatusCondition(&descheduling.Status.Conditions, metav1.Condition{
Type: v1alpha1.DeschedulingConditionReady,
@@ -106,7 +106,7 @@ func (e *DeschedulingsExecutor) Reconcile(ctx context.Context, req ctrl.Request)
// Don't touch processing deschedulings.
if meta.IsStatusConditionTrue(descheduling.Status.Conditions, v1alpha1.DeschedulingConditionInProgress) {
- log.Info("skipping descheduling, already in progress")
+ log.V(1).Info("skipping descheduling, already in progress")
return ctrl.Result{}, nil
}
@@ -157,17 +157,17 @@ func (e *DeschedulingsExecutor) Reconcile(ctx context.Context, req ctrl.Request)
}
return ctrl.Result{}, nil
}
- log.Info(
+ log.V(1).Info(
"descheduler: live-migrating virtual machine",
"vmId", vmId, "host", server.ComputeHost,
)
if !e.Conf.DisableDeschedulerDryRun {
- log.Info("descheduler: dry-run enabled, skipping execution", "vmId", vmId)
+ log.V(1).Info("descheduler: dry-run enabled, skipping execution", "vmId", vmId)
return ctrl.Result{}, nil
}
- log.Info("descheduler: executing migration for VM", "vmId", vmId)
+ log.V(1).Info("descheduler: executing migration for VM", "vmId", vmId)
if err := e.NovaClient.LiveMigrate(ctx, vmId); err != nil {
log.Error(err, "descheduler: failed to live-migrate VM", "vmId", vmId, "error", err)
old := descheduling.DeepCopy()
@@ -228,7 +228,7 @@ func (e *DeschedulingsExecutor) Reconcile(ctx context.Context, req ctrl.Request)
}
return ctrl.Result{}, nil
}
- log.Info(
+ log.V(1).Info(
"descheduler: waiting for live-migration to complete",
"vmId", vmId, "status", server.Status,
)
diff --git a/internal/scheduling/nova/detector_pipeline_controller.go b/internal/scheduling/nova/detector_pipeline_controller.go
index 71f062cc2..9bfa012ac 100644
--- a/internal/scheduling/nova/detector_pipeline_controller.go
+++ b/internal/scheduling/nova/detector_pipeline_controller.go
@@ -5,7 +5,6 @@ package nova
import (
"context"
- "log/slog"
"time"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -18,10 +17,14 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)
+// log is the shared package-level logger for the nova scheduler package.
+var schedulerLog = logf.Log.WithName("nova-scheduler")
+
// The deschedulings pipeline controller is responsible for periodically running
// the descheduling pipeline and creating descheduling resources based on the
// selections made.
@@ -66,28 +69,28 @@ func (c *DetectorPipelineController) CreateDeschedulingsPeriodically(ctx context
for {
select {
case <-ctx.Done():
- slog.Info("descheduler shutting down")
+ schedulerLog.Info("descheduler shutting down")
return
default:
// Get the pipeline for the current configuration.
p, ok := c.GetPipeline("kvm-descheduler")
if !ok {
- slog.Error("descheduler: pipeline not found or not ready yet")
+ schedulerLog.Error(nil, "descheduler: pipeline not found or not ready yet")
time.Sleep(jobloop.DefaultJitter(time.Minute))
continue
}
decisionsByStep := p.Run()
if len(decisionsByStep) == 0 {
- slog.Info("descheduler: no decisions made in this run")
+ schedulerLog.V(1).Info("descheduler: no decisions made in this run")
time.Sleep(jobloop.DefaultJitter(time.Minute))
continue
}
- slog.Info("descheduler: decisions made", "decisionsByStep", decisionsByStep)
+ schedulerLog.V(1).Info("descheduler: decisions made", "decisionsByStep", decisionsByStep)
decisions := p.Combine(decisionsByStep)
var err error
decisions, err = p.Breaker.Filter(ctx, decisions)
if err != nil {
- slog.Error("descheduler: failed to filter decisions for cycles", "error", err)
+ schedulerLog.Error(err, "descheduler: failed to filter decisions for cycles")
time.Sleep(jobloop.DefaultJitter(time.Minute))
continue
}
@@ -99,7 +102,7 @@ func (c *DetectorPipelineController) CreateDeschedulingsPeriodically(ctx context
var existing v1alpha1.Descheduling
err := p.Get(ctx, client.ObjectKey{Name: decision.VMID}, &existing)
if err == nil {
- slog.Info("descheduler: descheduling already exists for VM, skipping", "vmId", decision.VMID)
+ schedulerLog.V(1).Info("descheduler: descheduling already exists for VM, skipping", "vmId", decision.VMID)
continue
}
@@ -111,11 +114,11 @@ func (c *DetectorPipelineController) CreateDeschedulingsPeriodically(ctx context
descheduling.Spec.PrevHost = decision.Host
descheduling.Spec.Reason = decision.Reason
if err := p.Create(ctx, descheduling); err != nil {
- slog.Error("descheduler: failed to create descheduling", "error", err)
+ schedulerLog.Error(err, "descheduler: failed to create descheduling")
time.Sleep(jobloop.DefaultJitter(time.Minute))
continue
}
- slog.Info("descheduler: created descheduling", "vmId", decision.VMID, "host", decision.Host, "reason", decision.Reason)
+ schedulerLog.Info("descheduler: created descheduling", "vmId", decision.VMID, "host", decision.Host, "reason", decision.Reason)
}
time.Sleep(jobloop.DefaultJitter(time.Minute))
diff --git a/internal/scheduling/nova/e2e_checks.go b/internal/scheduling/nova/e2e_checks.go
index 94aaa8642..d8dc57d92 100644
--- a/internal/scheduling/nova/e2e_checks.go
+++ b/internal/scheduling/nova/e2e_checks.go
@@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"io"
- "log/slog"
"math/rand"
"net/http"
"strings"
@@ -136,7 +135,7 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
Connector{Client: client, HTTPClient: authenticatedHTTP}.
FromSecretRef(ctx, config.KeystoneSecretRef))
pc := authenticatedKeystone.Client()
- slog.Info("locating nova endpoint")
+ schedulerLog.Info("locating nova endpoint")
novaURL := must.Return(pc.EndpointLocator(gophercloud.EndpointOpts{
Type: "compute",
Availability: gophercloud.Availability(authenticatedKeystone.Availability()),
@@ -149,16 +148,16 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
// Since 2.61, the extra_specs are returned in the flavor details.
Microversion: "2.61",
}
- slog.Info("nova endpoint found", "novaURL", novaURL)
+ schedulerLog.Info("nova endpoint found", "novaURL", novaURL)
- slog.Info("listing hypervisors")
+ schedulerLog.Info("listing hypervisors")
hypervisors := must.Return(getHypervisors(ctx, novaSC))
if len(hypervisors) == 0 {
panic("no hypervisors found")
}
- slog.Info("found hypervisors", "count", len(hypervisors))
+ schedulerLog.Info("found hypervisors", "count", len(hypervisors))
- slog.Info("listing servers")
+ schedulerLog.Info("listing servers")
slo := servers.ListOpts{AllTenants: true, Limit: 10}
dataServers := &struct {
Servers []nova.Server `json:"servers"`
@@ -172,9 +171,9 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
if len(servers) == 0 {
panic("no servers found")
}
- slog.Info("found servers", "count", len(servers))
+ schedulerLog.Info("found servers", "count", len(servers))
- slog.Info("listing flavors")
+ schedulerLog.Info("listing flavors")
flo := flavors.ListOpts{AccessType: flavors.AllAccess}
pages := must.Return(flavors.ListDetail(novaSC, flo).AllPages(ctx))
dataFlavors := &struct {
@@ -188,12 +187,12 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
// See: https://github.com/sapcc/nova/blob/5fcb125/nova/utils.py#L1234
// And: https://github.com/sapcc/nova/pull/570/files
if strings.Contains(f.ExtraSpecs, "capabilities:cpu_arch") {
- slog.Info("skipping baremetal flavor", "flavorName", f.Name)
+ schedulerLog.Info("skipping baremetal flavor", "flavorName", f.Name)
continue
}
// The hypervisor_type extra spec is required to infer the correct pipeline.
if !strings.Contains(f.ExtraSpecs, "capabilities:hypervisor_type") {
- slog.Info("skipping flavor without hypervisor_type extra spec", "flavorName", f.Name)
+ schedulerLog.Info("skipping flavor without hypervisor_type extra spec", "flavorName", f.Name)
continue
}
flavors = append(flavors, f)
@@ -201,9 +200,9 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
if len(flavors) == 0 {
panic("no flavors found")
}
- slog.Info("found non-bm flavors", "count", len(flavors))
+ schedulerLog.Info("found non-bm flavors", "count", len(flavors))
- slog.Info("listing aggregates")
+ schedulerLog.Info("listing aggregates")
pages = must.Return(aggregates.List(novaSC).AllPages(ctx))
dataAggregates := &struct {
Aggregates []nova.RawAggregate `json:"aggregates"`
@@ -213,9 +212,9 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
if len(aggregates) == 0 {
panic("no aggregates found")
}
- slog.Info("found aggregates", "count", len(aggregates))
+ schedulerLog.Info("found aggregates", "count", len(aggregates))
- slog.Info("locating keystone endpoint")
+ schedulerLog.Info("locating keystone endpoint")
keystoneURL := must.Return(pc.EndpointLocator(gophercloud.EndpointOpts{
Type: "identity",
Availability: gophercloud.Availability(authenticatedKeystone.Availability()),
@@ -225,9 +224,9 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
Endpoint: keystoneURL,
Type: "identity",
}
- slog.Info("keystone endpoint found", "keystoneURL", keystoneURL)
+ schedulerLog.Info("keystone endpoint found", "keystoneURL", keystoneURL)
- slog.Info("listing projects")
+ schedulerLog.Info("listing projects")
pages = must.Return(projects.List(keystoneSC, projects.ListOpts{}).AllPages(ctx))
dataProjects := &struct {
Projects []identity.RawProject `json:"projects"`
@@ -237,9 +236,9 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
if len(projects) == 0 {
panic("no projects found")
}
- slog.Info("found projects", "count", len(projects))
+ schedulerLog.Info("found projects", "count", len(projects))
- slog.Info("listing domains")
+ schedulerLog.Info("listing domains")
pages = must.Return(domains.List(keystoneSC, nil).AllPages(ctx))
dataDomains := &struct {
Domains []identity.Domain `json:"domains"`
@@ -249,7 +248,7 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
if len(domains) == 0 {
panic("no domains found")
}
- slog.Info("found domains", "count", len(domains))
+ schedulerLog.Info("found domains", "count", len(domains))
azs := make(map[string]struct{})
for _, a := range aggregates {
@@ -265,7 +264,7 @@ func prepare(ctx context.Context, client client.Client, config ChecksConfig) dat
if len(azsSlice) == 0 {
panic("no availability zones found")
}
- slog.Info("found availability zones", "count", len(azsSlice))
+ schedulerLog.Info("found availability zones", "count", len(azsSlice))
return datacenter{
servers: servers,
@@ -295,12 +294,12 @@ func randomRequest(dc datacenter, seed int) api.ExternalSchedulerRequest {
}
// Take a random server. If we don't use an existing server the decision will be cleaned up.
server := dc.servers[randSource.Intn(len(dc.servers))]
- slog.Info("using server", "serverID", server.ID, "serverName", server.Name)
+ schedulerLog.Info("using server", "serverID", server.ID, "serverName", server.Name)
// Get a random az.
az := dc.azs[randSource.Intn(len(dc.azs))]
- slog.Info("using availability zone", "az", az)
+ schedulerLog.Info("using availability zone", "az", az)
project := dc.projects[randSource.Intn(len(dc.projects))]
- slog.Info("using project", "projectID", project.ID, "projectName", project.Name)
+ schedulerLog.Info("using project", "projectID", project.ID, "projectName", project.Name)
// Get the domain for the project.
domainsByID := make(map[string]identity.Domain)
for _, d := range dc.domains {
@@ -310,10 +309,10 @@ func randomRequest(dc datacenter, seed int) api.ExternalSchedulerRequest {
if !ok {
panic("project domain not found")
}
- slog.Info("using domain", "domainID", domain.ID, "domainName", domain.Name)
+ schedulerLog.Info("using domain", "domainID", domain.ID, "domainName", domain.Name)
// Get a random flavor.
flavor := dc.flavors[randSource.Intn(len(dc.flavors))]
- slog.Info("using flavor", "flavorName", flavor.Name, "flavorID", flavor.ID)
+ schedulerLog.Info("using flavor", "flavorName", flavor.Name, "flavorID", flavor.ID)
// JSON unmarshal the extra specs to a string.
var extraSpecs map[string]string
if flavor.ExtraSpecs == "" {
@@ -321,7 +320,7 @@ func randomRequest(dc datacenter, seed int) api.ExternalSchedulerRequest {
} else if err := json.Unmarshal([]byte(flavor.ExtraSpecs), &extraSpecs); err != nil {
panic(err)
}
- slog.Info("using flavor extra specs", "extraSpecs", extraSpecs)
+ schedulerLog.Info("using flavor extra specs", "extraSpecs", extraSpecs)
request := api.ExternalSchedulerRequest{
Spec: api.NovaObject[api.NovaSpec]{Data: api.NovaSpec{
// Actual server that exists in nova but with mocked random properties.
@@ -344,7 +343,7 @@ func randomRequest(dc datacenter, seed int) api.ExternalSchedulerRequest {
// Force to use the pipeline with all filters enabled for kvm flavors.
hvType, err := request.GetHypervisorType()
if err != nil {
- slog.Info("failed to determine hypervisor type, using default pipeline", "error", err)
+ schedulerLog.Info("failed to determine hypervisor type, using default pipeline", "error", err)
} else if hvType == api.HypervisorTypeQEMU || hvType == api.HypervisorTypeCH {
request.Pipeline = "kvm-general-purpose-load-balancing"
}
@@ -358,7 +357,7 @@ func checkNovaSchedulerReturnsValidHosts(
) []string {
apiURL := "http://localhost:8080/scheduler/nova/external"
- slog.Info("sending request to external scheduler", "apiURL", apiURL)
+ schedulerLog.Info("sending request to external scheduler", "apiURL", apiURL)
requestBody := must.Return(json.Marshal(req))
buf := bytes.NewBuffer(requestBody)
@@ -370,7 +369,7 @@ func checkNovaSchedulerReturnsValidHosts(
if respRaw.StatusCode != http.StatusOK {
// Log the response body for debugging
bodyBytes := must.Return(io.ReadAll(respRaw.Body))
- slog.Error("external scheduler API returned non-200 status code",
+ schedulerLog.Error(nil, "external scheduler API returned non-200 status code",
"statusCode", respRaw.StatusCode,
"responseBody", string(bodyBytes),
)
@@ -389,11 +388,11 @@ func checkNovaSchedulerReturnsValidHosts(
func CheckDomainNameHintRouting(ctx context.Context, config ChecksConfig) {
cfg := config.DomainNameHintCheck
if cfg == nil {
- slog.Info("domain_name hint check skipped: DomainNameHintCheck not configured")
+ schedulerLog.Info("domain_name hint check skipped: DomainNameHintCheck not configured")
return
}
if cfg.FlavorName == "" || len(cfg.EligibleHosts) == 0 {
- slog.Info("domain_name hint check skipped: FlavorName or EligibleHosts not set")
+ schedulerLog.Info("domain_name hint check skipped: FlavorName or EligibleHosts not set")
return
}
@@ -425,14 +424,14 @@ func CheckDomainNameHintRouting(ctx context.Context, config ChecksConfig) {
},
}
- slog.Info("domain_name hint check: sending CR reservation scheduling request",
+ schedulerLog.Info("domain_name hint check: sending CR reservation scheduling request",
"domainName", cfg.DomainName,
"eligibleHosts", cfg.EligibleHosts,
"flavorName", cfg.FlavorName,
)
hosts2 := checkNovaSchedulerReturnsValidHosts(ctx, req)
- slog.Info("domain_name hint check passed",
+ schedulerLog.Info("domain_name hint check passed",
"domainName", cfg.DomainName,
"hostsReturned", len(hosts2),
"hosts", hosts2,
@@ -453,7 +452,7 @@ func RunChecks(ctx context.Context, client client.Client, config ChecksConfig) {
requestsWithNoHostsReturned++
}
}
- slog.Info(
+ schedulerLog.Info(
"summary",
"requestsWithHostsReturned", requestsWithHostsReturned,
"requestsWithNoHostsReturned", requestsWithNoHostsReturned,
diff --git a/internal/scheduling/nova/external_scheduler_api.go b/internal/scheduling/nova/external_scheduler_api.go
index e4e68d27a..9e89f5f2a 100644
--- a/internal/scheduling/nova/external_scheduler_api.go
+++ b/internal/scheduling/nova/external_scheduler_api.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
"io"
- "log/slog"
"math/rand"
"net/http"
@@ -18,6 +17,7 @@ import (
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
scheduling "github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -104,12 +104,12 @@ func (httpAPI *httpAPI) canRunScheduler(requestData api.ExternalSchedulerRequest
func (httpAPI *httpAPI) inferPipelineName(requestData api.ExternalSchedulerRequest) (string, error) {
hvType, err := requestData.GetHypervisorType()
if err != nil {
- slog.Warn("failed to determine hypervisor type, cannot infer pipeline name", "error", err)
+ schedulerLog.Error(err, "failed to determine hypervisor type, cannot infer pipeline name")
return "", errors.New("failed to determine hypervisor type from request data")
}
flavorType, err := requestData.GetFlavorType()
if err != nil {
- slog.Warn("failed to determine flavor type, cannot infer pipeline name", "error", err)
+ schedulerLog.Error(err, "failed to determine flavor type, cannot infer pipeline name")
return "", errors.New("failed to determine flavor type from request data")
}
switch hvType {
@@ -148,7 +148,7 @@ func shuffleTopHosts(hosts []string, k int) []string {
rand.Shuffle(n, func(i, j int) { //nolint:gosec // non-crypto shuffle for load balancing
result[i], result[j] = result[j], result[i]
})
- slog.Info("shuffled top hosts for evacuation", "k", n, "hosts", result[:n])
+ schedulerLog.V(1).Info("shuffled top hosts for evacuation", "k", n, "hosts", result[:n])
return result
}
@@ -171,7 +171,7 @@ func limitHostsToRequest(request api.ExternalSchedulerRequest, hosts []string) [
if hostSet[host] {
limited = append(limited, host)
} else {
- slog.Warn("filtering out host from response since it wasn't in the request",
+ schedulerLog.V(1).Info("filtering out host from response since it wasn't in the request",
"host", host, "inRequest", request.Hosts)
}
}
@@ -189,7 +189,7 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req
// Exit early if the request method is not POST.
if r.Method != http.MethodPost {
internalErr := fmt.Errorf("invalid request method: %s", r.Method)
- c.Respond(nil, http.StatusMethodNotAllowed, internalErr, "invalid request method")
+ c.Respond(logr.Logger{}, http.StatusMethodNotAllowed, internalErr, "invalid request method")
return
}
@@ -199,7 +199,7 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req
// If configured, log out the complete request body.
body, err := io.ReadAll(r.Body)
if err != nil {
- c.Respond(nil, http.StatusInternalServerError, err, "failed to read request body")
+ c.Respond(logr.Logger{}, http.StatusInternalServerError, err, "failed to read request body")
return
}
raw := runtime.RawExtension{Raw: body}
@@ -208,15 +208,10 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req
cp := body
reader := bytes.NewReader(cp)
if err := json.NewDecoder(reader).Decode(&requestData); err != nil {
- c.Respond(nil, http.StatusBadRequest, err, "failed to decode request body")
+ c.Respond(logr.Logger{}, http.StatusBadRequest, err, "failed to decode request body")
return
}
- traceArgs := requestData.GetTraceLogArgs()
- traceArgsAny := make([]any, len(traceArgs))
- for i, a := range traceArgs {
- traceArgsAny[i] = a
- }
- logger := slog.With(traceArgsAny...)
+ logger := schedulerLog.WithValues(requestData.GetTraceLogArgs()...)
logger.Info("handling POST request", "url", "/scheduler/nova/external", "body", string(body))
// Replicate Nova's forced-destination behavior: when the request is forced
@@ -227,7 +222,7 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req
// for forced-destination requests.
if httpAPI.config.forcedDestinationEnabled() && requestData.IsForcedDestination() {
hosts := requestData.ForcedHosts()
- logger.Info("forced destination request, skipping filters", "hosts", hosts)
+ logger.V(1).Info("forced destination request, skipping filters", "hosts", hosts)
response := api.ExternalSchedulerResponse{Hosts: hosts}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
@@ -252,13 +247,13 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req
c.Respond(logger, http.StatusBadRequest, err, err.Error())
return
}
- logger.Info("inferred pipeline name", "pipeline", requestData.Pipeline)
+ logger.V(1).Info("inferred pipeline name", "pipeline", requestData.Pipeline)
}
intent, err := requestData.GetIntent()
if err != nil {
// Just log this in case the intent cannot be determined, but continue processing.
- logger.Info("found no request intent (intended for initial placements)")
+ logger.V(1).Info("found no request intent (intended for initial placements)")
intent = api.CreateIntent
}
@@ -297,7 +292,7 @@ func (httpAPI *httpAPI) NovaExternalScheduler(w http.ResponseWriter, r *http.Req
hosts := decision.Status.Result.OrderedHosts
if httpAPI.config.NovaLimitHostsToRequest {
hosts = limitHostsToRequest(requestData, hosts)
- logger.Info("limited hosts to request",
+ logger.V(1).Info("limited hosts to request",
"hosts", hosts, "originalHosts", decision.Status.Result.OrderedHosts)
}
// This is a hack to address the problem that Nova only uses the first host in hosts for evacuation requests.
diff --git a/internal/scheduling/nova/filter_weigher_pipeline_controller.go b/internal/scheduling/nova/filter_weigher_pipeline_controller.go
index 22553464d..a81074072 100644
--- a/internal/scheduling/nova/filter_weigher_pipeline_controller.go
+++ b/internal/scheduling/nova/filter_weigher_pipeline_controller.go
@@ -167,12 +167,12 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision
return &request, fmt.Errorf("pipeline config not found: %q", decision.Spec.PipelineRef.Name)
}
if pipelineConf.Spec.IgnorePreselection {
- log.Info("gathering all placement candidates before filtering")
+ log.V(1).Info("gathering all placement candidates before filtering")
if err := c.gatherer.MutateWithAllCandidates(ctx, &request); err != nil {
log.Error(err, "failed to gather all placement candidates")
return &request, err
}
- log.Info("gathered all placement candidates", "numHosts", len(request.Hosts))
+ log.V(1).Info("gathered all placement candidates", "numHosts", len(request.Hosts))
}
result, err := pipeline.Run(request)
diff --git a/internal/scheduling/nova/hypervisor_overcommit_controller.go b/internal/scheduling/nova/hypervisor_overcommit_controller.go
index 72e4507fc..08997d8ef 100644
--- a/internal/scheduling/nova/hypervisor_overcommit_controller.go
+++ b/internal/scheduling/nova/hypervisor_overcommit_controller.go
@@ -117,7 +117,7 @@ func (c *HypervisorOvercommitController) Reconcile(ctx context.Context, req ctrl
if apierrors.IsNotFound(err) {
// If the custom resource is not found then it usually means
// that it was deleted or not created.
- log.Info("Resource not found. Ignoring since object must be deleted")
+ log.V(1).Info("Resource not found. Ignoring since object must be deleted")
return ctrl.Result{}, nil
}
// Error reading the object - requeue the request.
diff --git a/internal/scheduling/nova/nova_client.go b/internal/scheduling/nova/nova_client.go
index c5ad68cc6..93f4988a2 100644
--- a/internal/scheduling/nova/nova_client.go
+++ b/internal/scheduling/nova/nova_client.go
@@ -7,7 +7,6 @@ import (
"context"
"encoding/json"
"errors"
- "log/slog"
"net/http"
"github.com/cobaltcore-dev/cortex/pkg/keystone"
@@ -106,7 +105,7 @@ func (api *novaClient) Init(ctx context.Context, client client.Client, conf Nova
if err != nil {
return err
}
- slog.Info("using nova endpoint", "url", url)
+ schedulerLog.V(1).Info("using nova endpoint", "url", url)
api.sc = &gophercloud.ServiceClient{
ProviderClient: provider,
Endpoint: url,
@@ -121,7 +120,7 @@ func (api *novaClient) Init(ctx context.Context, client client.Client, conf Nova
eo := gophercloud.EndpointOpts{Availability: gophercloud.Availability(authenticatedKeystone.Availability())}
api.osTypeProber, err = liquidapi.NewOSTypeProber(provider, eo)
if err != nil {
- slog.Warn("failed to initialize OS type prober - os_type will be empty", "error", err)
+ schedulerLog.Error(err, "failed to initialize OS type prober - os_type will be empty")
// Non-fatal - continue without OS type probing
}
@@ -187,7 +186,7 @@ func (api *novaClient) GetServerMigrations(ctx context.Context, id string) ([]mi
}
migrations = append(migrations, list.Migrations...)
}
- slog.Info("fetched migrations for vm", "id", id, "count", len(migrations))
+ schedulerLog.V(1).Info("fetched migrations for vm", "id", id, "count", len(migrations))
return migrations, nil
}
@@ -302,6 +301,6 @@ func (api *novaClient) ListProjectServers(ctx context.Context, projectID string)
}
}
- slog.Info("fetched servers for project", "projectID", projectID, "count", len(result))
+ schedulerLog.V(1).Info("fetched servers for project", "projectID", projectID, "count", len(result))
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/detectors/avoid_high_steal_pct.go b/internal/scheduling/nova/plugins/detectors/avoid_high_steal_pct.go
index 2921daf1c..9d2d41942 100644
--- a/internal/scheduling/nova/plugins/detectors/avoid_high_steal_pct.go
+++ b/internal/scheduling/nova/plugins/detectors/avoid_high_steal_pct.go
@@ -7,7 +7,6 @@ import (
"context"
"errors"
"fmt"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute"
@@ -15,8 +14,11 @@ import (
"github.com/cobaltcore-dev/cortex/internal/scheduling/nova/plugins"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("detectors")
+
type AvoidHighStealPctStepOpts struct {
// Max steal pct threshold above which VMs should be descheduled.
MaxStealPctOverObservedTimeSpan float64 `json:"maxStealPctOverObservedTimeSpan"`
@@ -48,7 +50,7 @@ func (s *AvoidHighStealPctStep) Init(ctx context.Context, client client.Client,
func (s *AvoidHighStealPctStep) Run() ([]plugins.VMDetection, error) {
if s.Options.MaxStealPctOverObservedTimeSpan <= 0 {
- slog.Info("skipping step because maxStealPctOverObservedTimeSpan is not set or <= 0")
+ log.V(1).Info("skipping step because maxStealPctOverObservedTimeSpan is not set or <= 0")
return nil, nil
}
// Get VMs matching the MaxStealPctOverObservedTimeSpan option.
@@ -73,7 +75,7 @@ func (s *AvoidHighStealPctStep) Run() ([]plugins.VMDetection, error) {
Reason: fmt.Sprintf("kvm monitoring indicates cpu steal pct %.2f%% which is above %.2f%% threshold", f.MaxStealTimePct, s.Options.MaxStealPctOverObservedTimeSpan),
Host: f.Host,
})
- slog.Info("vm marked for descheduling due to high cpu steal pct",
+ log.V(1).Info("vm marked for descheduling due to high cpu steal pct",
"instanceUUID", f.InstanceUUID,
"maxStealTimePct", f.MaxStealTimePct,
)
diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go
index 6a9273954..d223dfe5e 100644
--- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go
+++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
"strings"
@@ -20,7 +20,7 @@ type FilterAggregateMetadata struct {
// Restrict hosts to specific projects if they are in an aggregate that has
// the "filter_tenant_id" metadata key set.
-func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterAggregateMetadata) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// Capacity probe calls are synthetic and not placed on behalf of a tenant
// project, so they must ignore tenant pinning. Failover reservations DO respect
@@ -32,7 +32,7 @@ func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.Externa
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
@@ -60,11 +60,11 @@ func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.Externa
}
restrictedProjectsByHost[hv.Name] = append(restrictedProjectsByHost[hv.Name], projectID)
}
- traceLog.Info("host is in aggregate with filter_tenant_id, adding restriction",
+ traceLog.V(1).Info("host is in aggregate with filter_tenant_id, adding restriction",
"host", hv.Name, "aggregate", aggregate.Name, "key", key, "tenant_id", value)
}
if !foundFilter {
- traceLog.Info("aggregate does not have filter_tenant_id metadata, skipping",
+ traceLog.V(1).Info("aggregate does not have filter_tenant_id metadata, skipping",
"aggregate", aggregate.Name)
}
}
@@ -74,14 +74,14 @@ func (s *FilterAggregateMetadata) Run(traceLog *slog.Logger, request api.Externa
if !slices.Contains(restrictedProjects, request.Spec.Data.ProjectID) {
// Project is not allowed on this hypervisor, filter it out.
delete(result.Activations, host)
- traceLog.Info(
+ traceLog.V(1).Info(
"filtering host not allowing project based on aggregate metadata",
"host", host,
"project", request.Spec.Data.ProjectID,
"restricted_projects", restrictedProjects,
)
} else {
- traceLog.Info(
+ traceLog.V(1).Info(
"host allows project based on aggregate metadata, keeping",
"host", host,
"project", request.Spec.Data.ProjectID,
diff --git a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go
index faabd4f53..769d06729 100644
--- a/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_aggregate_metadata_test.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -485,7 +485,7 @@ func TestFilterAggregateMetadata_Run(t *testing.T) {
step := &FilterAggregateMetadata{}
step.Client = fakeClient
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -537,7 +537,7 @@ func TestFilterAggregateMetadata_Run_ClientError(t *testing.T) {
step := &FilterAggregateMetadata{}
step.Client = fakeClient
- _, err := step.Run(slog.Default(), request)
+ _, err := step.Run(logr.Discard(), request)
if err == nil {
t.Errorf("expected error when client fails, got none")
}
@@ -584,7 +584,7 @@ func TestFilterAggregateMetadata_SkipsForCapacityProbeIntent(t *testing.T) {
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -625,7 +625,7 @@ func TestFilterAggregateMetadata_AppliesForFailoverIntents(t *testing.T) {
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go b/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go
index f1bc8008e..5e1e51b5a 100644
--- a/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go
+++ b/internal/scheduling/nova/plugins/filters/filter_allowed_projects.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -19,7 +19,7 @@ type FilterAllowedProjectsStep struct {
// Lock certain hosts for certain projects, based on the hypervisor spec.
// Note that hosts without specified projects are still accessible.
-func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterAllowedProjectsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// Capacity probe calls are synthetic and not placed on behalf of a tenant
// project, so they must ignore tenant pinning. Failover reservations DO respect
@@ -29,13 +29,13 @@ func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.Exter
return result, nil
}
if request.Spec.Data.ProjectID == "" {
- traceLog.Info("no project ID in request, skipping filter")
+ traceLog.V(1).Info("no project ID in request, skipping filter")
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
@@ -45,13 +45,13 @@ func (s *FilterAllowedProjectsStep) Run(traceLog *slog.Logger, request api.Exter
}
if len(hv.Spec.AllowedProjects) == 0 {
// Hypervisor is available for all projects.
- traceLog.Info("host allows all projects, keeping", "host", hv.Name)
+ traceLog.V(1).Info("host allows all projects, keeping", "host", hv.Name)
continue
}
if !slices.Contains(hv.Spec.AllowedProjects, request.Spec.Data.ProjectID) {
// Project is not allowed on this hypervisor, filter it out.
delete(result.Activations, hv.Name)
- traceLog.Info(
+ traceLog.V(1).Info(
"filtering host not allowing project",
"host", hv.Name,
"project", request.Spec.Data.ProjectID,
diff --git a/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go b/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go
index a5a3e87c5..755ea9903 100644
--- a/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_allowed_projects_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -297,7 +297,7 @@ func TestFilterAllowedProjectsStep_Run(t *testing.T) {
WithScheme(scheme).
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -349,7 +349,7 @@ func TestFilterAllowedProjectsStep_SkipsForCapacityProbeIntent(t *testing.T) {
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -385,7 +385,7 @@ func TestFilterAllowedProjectsStep_AppliesForFailoverIntents(t *testing.T) {
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_capabilities.go b/internal/scheduling/nova/plugins/filters/filter_capabilities.go
index 7facd24ac..31277eeaa 100644
--- a/internal/scheduling/nova/plugins/filters/filter_capabilities.go
+++ b/internal/scheduling/nova/plugins/filters/filter_capabilities.go
@@ -6,7 +6,7 @@ package filters
import (
"context"
"fmt"
- "log/slog"
+ "github.com/go-logr/logr"
"strings"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -45,11 +45,11 @@ func hvToNovaCapabilities(hv hv1.Hypervisor) (map[string]string, error) {
// Check the capabilities of each host and if they match the extra spec provided
// in the request spec flavor.
-func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterCapabilitiesStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
extraSpecs := request.Spec.Data.Flavor.Data.ExtraSpecs
if len(extraSpecs) == 0 {
- traceLog.Debug("no flavor extra spec capabilities in request, skipping filter")
+ traceLog.V(1).Info("no flavor extra spec capabilities in request, skipping filter")
return result, nil
}
@@ -62,7 +62,7 @@ func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.External
}
}
if len(requestedCapabilities) == 0 {
- traceLog.Debug("no capabilities in flavor extra specs, skipping filter")
+ traceLog.V(1).Info("no capabilities in flavor extra specs, skipping filter")
return result, nil
}
@@ -76,7 +76,7 @@ func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.External
for key, expr := range requestedCapabilities {
for _, op := range unsupportedOps {
if strings.Contains(expr, op) {
- traceLog.Warn(
+ traceLog.V(1).Info(
"unsupported extra spec operator in capabilities filter, skipping filter",
"key", key, "expr", expr, "flavor", request.Spec.Data.Flavor,
)
@@ -87,7 +87,7 @@ func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.External
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
@@ -98,7 +98,7 @@ func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.External
}
caps, err := hvToNovaCapabilities(hv)
if err != nil {
- traceLog.Warn("hypervisor has unknown capabilities, using empty defaults", "host", hv.Name, "error", err)
+ traceLog.Error(err, "hypervisor has unknown capabilities, using empty defaults", "host", hv.Name)
result.Events = append(result.Events, lib.FilterWeigherPipelineStepEvent{
Name: "filter_capabilities_unknown_hypervisor_type",
Labels: map[string]string{"hypervisor_type": hv.Status.DomainCapabilities.HypervisorType},
@@ -107,20 +107,20 @@ func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.External
}
hvCaps[hv.Name] = caps
}
- traceLog.Info("looking for capabilities", "capabilities", hvCaps)
+ traceLog.V(1).Info("looking for capabilities", "capabilities", hvCaps)
// Check which hosts match the requested capabilities.
for host := range result.Activations {
provided, ok := hvCaps[host]
if !ok {
delete(result.Activations, host)
- traceLog.Info("filtering host without provided capabilities", "host", host)
+ traceLog.V(1).Info("filtering host without provided capabilities", "host", host)
continue
}
// Check if the provided capabilities match the requested ones.
for keyRequested, valueRequested := range requestedCapabilities {
if providedValue, ok := provided[keyRequested]; !ok || providedValue != valueRequested {
- traceLog.Info(
+ traceLog.V(1).Info(
"filtering host with mismatched capabilities", "host", host,
"wantKey", keyRequested, "wantValue", valueRequested,
"haveKey?", ok, "haveValue", providedValue,
@@ -129,7 +129,7 @@ func (s *FilterCapabilitiesStep) Run(traceLog *slog.Logger, request api.External
break
}
}
- traceLog.Info("host matches requested capabilities, keeping", "host", host)
+ traceLog.V(1).Info("host matches requested capabilities, keeping", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go b/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go
index e63d68885..622932bb8 100644
--- a/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_capabilities_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -653,7 +653,7 @@ func TestFilterCapabilitiesStep_Run(t *testing.T) {
WithScheme(scheme).
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -751,7 +751,7 @@ func TestFilterCapabilitiesStep_DoesNotMutateExtraSpecs(t *testing.T) {
WithObjects(hvs...).
Build()
- _, err = step.Run(slog.Default(), request)
+ _, err = step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_correct_az.go b/internal/scheduling/nova/plugins/filters/filter_correct_az.go
index 465e01546..882cf261c 100644
--- a/internal/scheduling/nova/plugins/filters/filter_correct_az.go
+++ b/internal/scheduling/nova/plugins/filters/filter_correct_az.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
@@ -18,16 +18,16 @@ type FilterCorrectAZStep struct {
}
// Only get hosts in the requested az.
-func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterCorrectAZStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
if request.Spec.Data.AvailabilityZone == "" {
- traceLog.Info("no availability zone requested, skipping filter_correct_az step")
+ traceLog.V(1).Info("no availability zone requested, skipping filter_correct_az step")
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
// The availability zone is provided by the label
@@ -39,7 +39,7 @@ func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSch
}
az, ok := hv.Labels[corev1.LabelTopologyZone]
if !ok {
- traceLog.Warn("host missing zone label, keeping", "host", hv.Name)
+ traceLog.V(1).Info("host missing zone label, keeping", "host", hv.Name)
continue
}
if az == request.Spec.Data.AvailabilityZone {
@@ -49,18 +49,18 @@ func (s *FilterCorrectAZStep) Run(traceLog *slog.Logger, request api.ExternalSch
}
}
- traceLog.Info(
+ traceLog.V(1).Info(
"hosts inside requested az",
"availabilityZone", request.Spec.Data.AvailabilityZone,
"hosts", computeHostsInAZ,
)
for host := range result.Activations {
if _, ok := computeHostsInAZ[host]; ok {
- traceLog.Info("host is in requested az, keeping", "host", host)
+ traceLog.V(1).Info("host is in requested az, keeping", "host", host)
continue
}
delete(result.Activations, host)
- traceLog.Info("filtering host outside requested az", "host", host)
+ traceLog.V(1).Info("filtering host outside requested az", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go b/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go
index 9b4df454e..135766806 100644
--- a/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_correct_az_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -171,7 +171,7 @@ func TestFilterCorrectAZStep_Run(t *testing.T) {
WithScheme(scheme).
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go
index 231efa9aa..c35a63cff 100644
--- a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go
+++ b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
@@ -28,14 +28,14 @@ type FilterExcludeHostsStepOpts struct {
func (opts FilterExcludeHostsStepOpts) Validate() error { return nil }
func (s *FilterExcludeHostsStep) Run(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
request api.ExternalSchedulerRequest,
) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
for _, host := range s.Options.ExcludedHosts {
delete(result.Activations, host) // noop if host is not in the map
- traceLog.Info("filtering out host based on excluded hosts configuration", "host", host)
+ traceLog.V(1).Info("filtering out host based on excluded hosts configuration", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go
index 0c9e35c59..45676b731 100644
--- a/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_exclude_hosts_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -218,7 +218,7 @@ func TestFilterExcludeHostsStep_Run(t *testing.T) {
ExcludedHosts: tt.excludedHosts,
}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_external_customer.go b/internal/scheduling/nova/plugins/filters/filter_external_customer.go
index 80bd7cf9c..19a412797 100644
--- a/internal/scheduling/nova/plugins/filters/filter_external_customer.go
+++ b/internal/scheduling/nova/plugins/filters/filter_external_customer.go
@@ -6,7 +6,7 @@ package filters
import (
"context"
"errors"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
"strings"
@@ -34,7 +34,7 @@ type FilterExternalCustomerStep struct {
// Prefix-match the domain name for external customer domains and filter out hosts
// that are not intended for external customers.
-func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterExternalCustomerStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// Failover and capacity probe calls are not associated with a user domain;
@@ -45,17 +45,17 @@ func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.Exte
api.ReuseFailoverReservationIntent,
api.CapacityProbeIntent,
}, intent) {
- traceLog.Info("skipping external customer filter for non-placement intent", "intent", intent)
+ traceLog.V(1).Info("skipping external customer filter for non-placement intent", "intent", intent)
return result, nil
}
domainName, err := request.Spec.Data.GetSchedulerHintStr("domain_name")
if err != nil {
- traceLog.Warn("failed to get domain_name scheduler hint, skipping filter", "error", err)
+ traceLog.Error(err, "failed to get domain_name scheduler hint, skipping filter")
return result, nil
}
if slices.Contains(s.Options.CustomerIgnoredDomainNames, domainName) {
- traceLog.Info("domain is no external customer domain, skipping filter", "domain", domainName)
+ traceLog.V(1).Info("domain is no external customer domain, skipping filter", "domain", domainName)
return result, nil
}
found := false
@@ -66,13 +66,13 @@ func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.Exte
}
}
if !found {
- traceLog.Info("domain does not match any external customer prefix -- skipping filter", "domain", domainName)
+ traceLog.V(1).Info("domain does not match any external customer prefix -- skipping filter", "domain", domainName)
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsWithTrait := make(map[string]struct{})
@@ -88,14 +88,14 @@ func (s *FilterExternalCustomerStep) Run(traceLog *slog.Logger, request api.Exte
hvsWithTrait[hv.Name] = struct{}{}
}
- traceLog.Info("hosts supporting external customers", "hosts", hvsWithTrait)
+ traceLog.V(1).Info("hosts supporting external customers", "hosts", hvsWithTrait)
for host := range result.Activations {
if _, ok := hvsWithTrait[host]; ok {
- traceLog.Info("host supports external customers, keeping", "host", host)
+ traceLog.V(1).Info("host supports external customers, keeping", "host", host)
continue
}
delete(result.Activations, host)
- traceLog.Info("filtering host not supporting external customers", "host", host)
+ traceLog.V(1).Info("filtering host not supporting external customers", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go b/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go
index cb358e0c1..4d2ebe784 100644
--- a/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_external_customer_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -456,7 +456,7 @@ func TestFilterExternalCustomerStep_Run(t *testing.T) {
Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if tt.expectError {
if err == nil {
t.Errorf("expected error but got none")
@@ -576,7 +576,7 @@ func TestFilterExternalCustomerStep_SkipsForNonPlacementIntent(t *testing.T) {
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go b/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go
index 2a3c8e4ff..ad3c20288 100644
--- a/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go
+++ b/internal/scheduling/nova/plugins/filters/filter_has_accelerators.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -18,17 +18,17 @@ type FilterHasAcceleratorsStep struct {
}
// If requested, only get hosts with accelerators.
-func (s *FilterHasAcceleratorsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterHasAcceleratorsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
extraSpecs := request.Spec.Data.Flavor.Data.ExtraSpecs
if _, ok := extraSpecs["accel:device_profile"]; !ok {
- traceLog.Info("no accelerators requested, skipping filter")
+ traceLog.V(1).Info("no accelerators requested, skipping filter")
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsWithTrait := make(map[string]struct{})
@@ -44,13 +44,13 @@ func (s *FilterHasAcceleratorsStep) Run(traceLog *slog.Logger, request api.Exter
hvsWithTrait[hv.Name] = struct{}{}
}
- traceLog.Info("hosts with accelerators", "hosts", hvsWithTrait)
+ traceLog.V(1).Info("hosts with accelerators", "hosts", hvsWithTrait)
for host := range result.Activations {
if _, ok := hvsWithTrait[host]; ok {
continue
}
delete(result.Activations, host)
- traceLog.Info("filtering host without accelerators", "host", host)
+ traceLog.V(1).Info("filtering host without accelerators", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go b/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go
index 8610e3132..2c92cf81b 100644
--- a/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_has_accelerators_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -349,7 +349,7 @@ func TestFilterHasAcceleratorsStep_Run(t *testing.T) {
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go
index b3caa38b8..6400d5362 100644
--- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go
+++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity.go
@@ -6,9 +6,10 @@ package filters
import (
"context"
"errors"
- "log/slog"
"slices"
+ "github.com/go-logr/logr"
+
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
@@ -62,7 +63,7 @@ type FilterHasEnoughCapacity struct {
// known at this point.
//
// Please also note that disk space is currently not considered by this filter.
-func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterHasEnoughCapacity) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
opts := request.GetOptions()
result := s.IncludeAllHostsFromRequest(request)
@@ -78,7 +79,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
// and then subtract the actual resource allocation from virtual machines.
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
for _, hv := range hvs.Items {
@@ -87,7 +88,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
}
var sourceMap map[hv1.ResourceName]resource.Quantity
if hv.Status.EffectiveCapacity == nil {
- traceLog.Warn("hypervisor with nil effective capacity, use capacity instead (overprovisioning not considered)", "host", hv.Name)
+ traceLog.V(1).Info("hypervisor with nil effective capacity, use capacity instead (overprovisioning not considered)", "host", hv.Name)
sourceMap = hv.Status.Capacity
} else {
// Start with the total effective capacity which is capacity * overcommit ratio.
@@ -106,6 +107,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
free, ok := freeResourcesByHost[hv.Name][resourceName]
if !ok {
traceLog.Error(
+ nil,
"hypervisor with allocation for unknown resource",
"host", hv.Name, "resource", resourceName,
)
@@ -125,7 +127,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
for _, reservation := range reservations.Items {
// Check if this reservation type should be ignored — applies regardless of ready state.
if slices.Contains(ignoredReservationTypes, reservation.Spec.Type) {
- traceLog.Debug("ignoring reservation type", "type", reservation.Spec.Type, "reservation", reservation.Name)
+ traceLog.V(1).Info("ignoring reservation type", "type", reservation.Spec.Type, "reservation", reservation.Name)
continue
}
@@ -138,7 +140,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
// Block the full slot now to prevent a concurrent scheduling call from picking the
// same host. Unlock logic is intentionally skipped — an unconfirmed slot must not
// be unlocked for any VM or project.
- traceLog.Warn("reservation has target host set but is not yet ready — blocking host to prevent double-booking",
+ traceLog.V(1).Info("reservation has target host set but is not yet ready — blocking host to prevent double-booking",
"reservation", reservation.Name,
"targetHost", reservation.Spec.TargetHost,
"statusHost", reservation.Status.Host,
@@ -158,7 +160,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
intent, err := request.GetIntent()
switch {
case err == nil && intent == api.ReserveForCommittedResourceIntent:
- traceLog.Debug("keeping CR reservation locked for CR reservation scheduling",
+ traceLog.V(1).Info("keeping CR reservation locked for CR reservation scheduling",
"reservation", reservation.Name,
"intent", intent)
// Don't continue - fall through to block the resources
@@ -167,7 +169,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
// 1. Project ID matches
// 2. ResourceGroup matches the flavor's hw_version
reservation.Spec.CommittedResourceReservation.MatchesGroup(request.Spec.Data.ProjectID, request.Spec.Data.Flavor.Data.ExtraSpecs["quota:hw_version"]):
- traceLog.Info("unlocking resources reserved by matching committed resource reservation with allocation",
+ traceLog.V(1).Info("unlocking resources reserved by matching committed resource reservation with allocation",
"reservation", reservation.Name,
"instanceUUID", request.Spec.Data.InstanceUUID,
"projectID", request.Spec.Data.ProjectID,
@@ -188,7 +190,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
case api.EvacuateIntent:
if reservation.Status.FailoverReservation != nil {
if _, contained := reservation.Status.FailoverReservation.Allocations[request.Spec.Data.InstanceUUID]; contained {
- traceLog.Info("unlocking resources reserved by failover reservation for VM in allocations (evacuation)",
+ traceLog.V(1).Info("unlocking resources reserved by failover reservation for VM in allocations (evacuation)",
"reservation", reservation.Name,
"instanceUUID", request.Spec.Data.InstanceUUID)
continue
@@ -197,12 +199,12 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
case api.ReuseFailoverReservationIntent:
// Reuse check: the reservation already pre-blocks the right capacity for this VM.
// Don't subtract it from free capacity to avoid double-counting.
- traceLog.Debug("skipping failover reservation block for reuse compatibility check",
+ traceLog.V(1).Info("skipping failover reservation block for reuse compatibility check",
"reservation", reservation.Name)
continue
}
}
- traceLog.Debug("processing failover reservation", "reservation", reservation.Name)
+ traceLog.V(1).Info("processing failover reservation", "reservation", reservation.Name)
}
}
@@ -218,7 +220,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
hostsToBlock[reservation.Status.Host] = struct{}{}
}
if len(hostsToBlock) == 0 {
- traceLog.Debug("skipping reservation with no host", "reservation", reservation.Name)
+ traceLog.V(1).Info("skipping reservation with no host", "reservation", reservation.Name)
continue
}
@@ -238,7 +240,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
for host := range hostsToBlock {
// Skip hosts that don't have a corresponding Hypervisor resource.
if _, hostExists := freeResourcesByHost[host]; !hostExists {
- traceLog.Debug("skipping reservation for unknown host",
+ traceLog.V(1).Info("skipping reservation for unknown host",
"reservation", reservation.Name,
"host", host)
continue
@@ -247,7 +249,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
if freeCPU, exists := freeResourcesByHost[host]["cpu"]; exists {
freeCPU.Sub(cpu)
if freeCPU.Value() < 0 {
- traceLog.Warn("negative free CPU after blocking reservation",
+ traceLog.V(1).Info("negative free CPU after blocking reservation",
"host", host,
"reservation", reservation.Name,
"reservationType", reservation.Spec.Type,
@@ -262,7 +264,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
if freeMemory, exists := freeResourcesByHost[host]["memory"]; exists {
freeMemory.Sub(memory)
if freeMemory.Value() < 0 {
- traceLog.Warn("negative free memory after blocking reservation",
+ traceLog.V(1).Info("negative free memory after blocking reservation",
"host", host,
"reservation", reservation.Name,
"reservationType", reservation.Spec.Type,
@@ -286,10 +288,11 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
}
freeCPU, ok := free["cpu"]
if !ok || freeCPU.Value() < 0 {
- traceLog.Warn(
+ traceLog.V(1).Info(
"host with invalid CPU capacity",
"host", host, "freeCPU", freeCPU.String(),
)
+ delete(result.Activations, host) // host is already overloaded, remove from activations
continue
}
// Calculate how many instances can fit on this host, based on cpu.
@@ -297,7 +300,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
vcpuSlots := uint64(freeCPU.Value()) /
request.Spec.Data.Flavor.Data.VCPUs
if vcpuSlots < request.Spec.Data.NumInstances {
- traceLog.Info(
+ traceLog.V(1).Info(
"filtering host due to insufficient CPU capacity",
"host", host, "requested", request.Spec.Data.Flavor.Data.VCPUs,
"available", freeCPU.String(),
@@ -312,10 +315,11 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
}
freeMemory, ok := free["memory"]
if !ok || freeMemory.Value() < 0 {
- traceLog.Error(
+ traceLog.V(1).Info(
"host with invalid memory capacity",
"host", host, "freeMemory", freeMemory.String(),
)
+ delete(result.Activations, host) // host is already overloaded, remove from activations
continue
}
// Calculate how many instances can fit on this host, based on memory.
@@ -325,7 +329,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
memorySlots := uint64(freeMemory.Value()) /
(request.Spec.Data.Flavor.Data.MemoryMB * (1 << 20))
if memorySlots < request.Spec.Data.NumInstances {
- traceLog.Info(
+ traceLog.V(1).Info(
"filtering host due to insufficient RAM capacity",
"host", host, "requested_mb", request.Spec.Data.Flavor.Data.MemoryMB,
"available_mb", freeMemory.String(),
@@ -333,7 +337,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
delete(result.Activations, host)
continue
}
- traceLog.Info(
+ traceLog.V(1).Info(
"host has enough capacity", "host", host,
"requested_cpus", request.Spec.Data.Flavor.Data.VCPUs,
"available_cpus", freeCPU.String(),
@@ -346,7 +350,7 @@ func (s *FilterHasEnoughCapacity) Run(traceLog *slog.Logger, request api.Externa
for host := range result.Activations {
if _, ok := hostsEncountered[host]; !ok {
delete(result.Activations, host)
- traceLog.Info(
+ traceLog.V(1).Info(
"removing host with unknown capacity",
"host", host,
)
diff --git a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go
index dea74d093..bdde34cca 100644
--- a/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_has_enough_capacity_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -661,7 +661,7 @@ func TestFilterHasEnoughCapacity_ReservationTypes(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -737,7 +737,7 @@ func TestFilterHasEnoughCapacity_LockReservations(t *testing.T) {
step.Client = fakeClient
step.Options = FilterHasEnoughCapacityOpts{LockReserved: tt.yamlLockReserved}
request.Options = scheduling.Options{LockReservations: tt.optsLockReserved}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -952,7 +952,7 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes(t *testing.T) {
IgnoredReservationTypes: tt.ignoredReservationTypes,
}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -981,7 +981,7 @@ func TestFilterHasEnoughCapacity_AssumeEmptyHosts(t *testing.T) {
step.Options = FilterHasEnoughCapacityOpts{}
// Without AssumeEmptyHosts: host1 filtered (only 2 CPU free).
- resultWithout, err := step.Run(slog.Default(), request)
+ resultWithout, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -989,7 +989,7 @@ func TestFilterHasEnoughCapacity_AssumeEmptyHosts(t *testing.T) {
// With AssumeEmptyHosts via call-time options: allocations ignored → host1 passes.
request.Options = scheduling.Options{AssumeEmptyHosts: true}
- resultWith, err := step.Run(slog.Default(), request)
+ resultWith, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -1027,7 +1027,7 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes_CallTime(t *testing.T)
IgnoredReservationTypes: []v1alpha1.ReservationType{v1alpha1.ReservationTypeCommittedResource},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -1069,7 +1069,7 @@ func TestFilterHasEnoughCapacity_IgnoredReservationTypes_CallTimeMerge(t *testin
IgnoredReservationTypes: []v1alpha1.ReservationType{v1alpha1.ReservationTypeFailover},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -1231,7 +1231,7 @@ func TestFilterHasEnoughCapacity_ReserveForCommittedResourceIntent(t *testing.T)
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -1281,7 +1281,7 @@ func TestFilterHasEnoughCapacity_PlannedCRDoesNotBlock(t *testing.T) {
step.Options = FilterHasEnoughCapacityOpts{LockReserved: false}
request := newNovaRequest("instance-123", "project-A", "m1.large", "gp-1", 4, "8Gi", false, []string{"host1"})
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -1354,7 +1354,7 @@ func TestFilterHasEnoughCapacity_NilEffectiveCapacityFallback(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = FilterHasEnoughCapacityOpts{LockReserved: false}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -1363,6 +1363,126 @@ func TestFilterHasEnoughCapacity_NilEffectiveCapacityFallback(t *testing.T) {
}
}
+// TestFilterHasEnoughCapacity_CapacityBoundaries exercises every boundary of the CPU and memory
+// capacity checks, including overloaded hosts (allocation > effective capacity).
+//
+// Regression: the negative-free branches called continue without delete(result.Activations, host),
+// so an overloaded host silently passed the filter. The negative guard also exists to prevent
+// the value from being cast to uint64, which would wrap to a huge positive number.
+//
+// Flavor for all cases: 4 vCPUs, 8Gi (= 8192 MiB) memory.
+// CPU cases keep memory ample (100Gi free); memory cases keep CPU ample (100 free).
+// Each case is a single host "h"; effective capacity minus allocation gives the free value.
+func TestFilterHasEnoughCapacity_CapacityBoundaries(t *testing.T) {
+ scheme := buildTestScheme(t)
+
+ const (
+ flavorVCPUs = 4
+ flavorMem = "8Gi" // 8192 MiB
+ )
+
+ tests := []struct {
+ name string
+ hv *hv1.Hypervisor
+ wantPass bool
+ }{
+ // CPU boundary — memory always ample (100Gi free).
+ {
+ // free = 0 - 1 = -1: overloaded, hits underflow guard before uint64 cast
+ name: "cpu -1 (overloaded: alloc > effective capacity)",
+ hv: newHypervisor("h", "0", "1", "100Gi", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 0: zero slots
+ name: "cpu 0",
+ hv: newHypervisor("h", "0", "0", "100Gi", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 1 < flavor(4): floor(1/4) = 0 slots
+ name: "cpu 1 (below flavor)",
+ hv: newHypervisor("h", "1", "0", "100Gi", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 3 = flavor-1: floor(3/4) = 0 slots
+ name: "cpu 3 (one below flavor floor)",
+ hv: newHypervisor("h", "3", "0", "100Gi", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 4 = flavor: floor(4/4) = 1 slot
+ name: "cpu 4 (exactly flavor)",
+ hv: newHypervisor("h", "4", "0", "100Gi", "0"),
+ wantPass: true,
+ },
+ {
+ // free = 5 = flavor+1: floor(5/4) = 1 slot
+ name: "cpu 5 (one above flavor)",
+ hv: newHypervisor("h", "5", "0", "100Gi", "0"),
+ wantPass: true,
+ },
+
+ // Memory boundary — CPU always ample (100 free).
+ {
+ // free = 0 - 1Ki = -1Ki: overloaded, hits underflow guard before uint64 cast
+ name: "memory -1Ki (overloaded: alloc > effective capacity)",
+ hv: newHypervisor("h", "100", "0", "0", "1Ki"),
+ wantPass: false,
+ },
+ {
+ // free = 0: zero slots
+ name: "memory 0",
+ hv: newHypervisor("h", "100", "0", "0", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 1Mi << flavor(8Gi): floor(1Mi / 8Gi) = 0 slots
+ name: "memory 1Mi (below flavor)",
+ hv: newHypervisor("h", "100", "0", "1Mi", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 8191Mi = 8Gi-1Mi: floor(8191Mi / 8192Mi) = 0 slots
+ name: "memory 8191Mi (one MiB below flavor floor)",
+ hv: newHypervisor("h", "100", "0", "8191Mi", "0"),
+ wantPass: false,
+ },
+ {
+ // free = 8Gi = flavor: floor(8Gi / 8Gi) = 1 slot
+ name: "memory 8Gi (exactly flavor)",
+ hv: newHypervisor("h", "100", "0", "8Gi", "0"),
+ wantPass: true,
+ },
+ {
+ // free = 8193Mi = 8Gi+1Mi: floor(8193Mi / 8192Mi) = 1 slot
+ name: "memory 8193Mi (one MiB above flavor)",
+ hv: newHypervisor("h", "100", "0", "8193Mi", "0"),
+ wantPass: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ step := &FilterHasEnoughCapacity{}
+ step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.hv.DeepCopy()).Build()
+ step.Options = FilterHasEnoughCapacityOpts{LockReserved: false}
+
+ req := newNovaRequest("inst-1", "project-A", "m1.small", "gp-1", flavorVCPUs, flavorMem, false, []string{"h"})
+ result, err := step.Run(logr.Discard(), req)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if tt.wantPass {
+ assertActivations(t, result.Activations, []string{"h"}, []string{})
+ } else {
+ assertActivations(t, result.Activations, []string{}, []string{"h"})
+ }
+ })
+ }
+}
+
// TestFilterHasEnoughCapacity_VMInterReservationMigration covers all realistic phases of a VM
// migrating from res-a (on hv-a) to res-b (on hv-b).
//
@@ -1553,7 +1673,7 @@ func TestFilterHasEnoughCapacity_VMInterReservationMigration(t *testing.T) {
step.Options = FilterHasEnoughCapacityOpts{LockReserved: false}
request := newNovaRequest("instance-new", thirdParty, "m1.small", flavorGroup, 3, "6Gi", false, []string{"hv-a", "hv-b"})
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go
index ac7ed8748..d6d322707 100644
--- a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go
+++ b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
"strings"
@@ -21,34 +21,34 @@ type FilterHasRequestedTraits struct {
// Filter hosts that do not have the requested traits given by the extra spec:
// - "trait:": "forbidden" means the host must not have the specified trait.
// - "trait:": "required" means the host must have the specified trait.
-func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterHasRequestedTraits) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
var requiredTraits, forbiddenTraits []string
for key, value := range request.Spec.Data.Flavor.Data.ExtraSpecs {
if !strings.HasPrefix(key, "trait:") {
- traceLog.Info("ignoring extra spec that does not start with trait:", "key", key)
+ traceLog.V(1).Info("ignoring extra spec that does not start with trait:", "key", key)
continue
}
traitName := strings.TrimPrefix(key, "trait:")
switch value {
case "forbidden":
forbiddenTraits = append(forbiddenTraits, traitName)
- traceLog.Info("trait marked forbidden, will filter out hosts with this trait",
+ traceLog.V(1).Info("trait marked forbidden, will filter out hosts with this trait",
"trait", traitName)
case "required":
requiredTraits = append(requiredTraits, traitName)
- traceLog.Info("trait marked required, will filter out hosts without this trait",
+ traceLog.V(1).Info("trait marked required, will filter out hosts without this trait",
"trait", traitName)
default:
- traceLog.Info("ignoring trait with unsupported value",
+ traceLog.V(1).Info("ignoring trait with unsupported value",
"trait", traitName, "value", value)
}
}
if len(requiredTraits) == 0 && len(forbiddenTraits) == 0 {
- traceLog.Info("no traits requested, skipping filter")
+ traceLog.V(1).Info("no traits requested, skipping filter")
return result, nil
}
- traceLog.Info(
+ traceLog.V(1).Info(
"filtering hosts based on requested traits",
"required", requiredTraits,
"forbidden", forbiddenTraits,
@@ -56,7 +56,7 @@ func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.Extern
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
@@ -88,11 +88,11 @@ func (s *FilterHasRequestedTraits) Run(traceLog *slog.Logger, request api.Extern
for host := range result.Activations {
if _, ok := hostsMatchingAllTraits[host]; ok {
- traceLog.Info("host matches requested traits, keeping", "host", host)
+ traceLog.V(1).Info("host matches requested traits, keeping", "host", host)
continue
}
delete(result.Activations, host)
- traceLog.Info("filtering host not matching requested traits", "host", host)
+ traceLog.V(1).Info("filtering host not matching requested traits", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go
index 4b57737da..719aa37fd 100644
--- a/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_has_requested_traits_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -463,7 +463,7 @@ func TestFilterHasRequestedTraits_Run(t *testing.T) {
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_host_instructions.go b/internal/scheduling/nova/plugins/filters/filter_host_instructions.go
index dafb6675f..c4121c22d 100644
--- a/internal/scheduling/nova/plugins/filters/filter_host_instructions.go
+++ b/internal/scheduling/nova/plugins/filters/filter_host_instructions.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -18,25 +18,25 @@ type FilterHostInstructionsStep struct {
// Filter hosts based on instructions given in the request spec. Supported are:
// - spec.ignore_hosts: Filter out all hosts in this list.
// - spec.force_hosts: Include only hosts in this list.
-func (s *FilterHostInstructionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterHostInstructionsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
if request.Spec.Data.IgnoreHosts != nil {
for _, host := range *request.Spec.Data.IgnoreHosts {
delete(result.Activations, host)
- traceLog.Info("filtering host which is ignored", "host", host)
+ traceLog.V(1).Info("filtering host which is ignored", "host", host)
}
} else {
- traceLog.Info("no hosts to ignore specified")
+ traceLog.V(1).Info("no hosts to ignore specified")
}
if request.Spec.Data.ForceHosts != nil {
for host := range result.Activations {
if !slices.Contains(*request.Spec.Data.ForceHosts, host) {
delete(result.Activations, host)
- traceLog.Info("filtering host which is not forced", "host", host)
+ traceLog.V(1).Info("filtering host which is not forced", "host", host)
}
}
} else {
- traceLog.Info("no forced hosts specified")
+ traceLog.V(1).Info("no forced hosts specified")
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go b/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go
index 10bcb60c9..349932c3c 100644
--- a/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_host_instructions_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -331,7 +331,7 @@ func TestFilterHostInstructionsStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
step := &FilterHostInstructionsStep{}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_image_properties.go b/internal/scheduling/nova/plugins/filters/filter_image_properties.go
index dfc0d6446..f4467c828 100644
--- a/internal/scheduling/nova/plugins/filters/filter_image_properties.go
+++ b/internal/scheduling/nova/plugins/filters/filter_image_properties.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -19,7 +19,7 @@ type FilterImagePropertiesStep struct {
}
// Filter hosts based on image properties given in the request spec.
-func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterImagePropertiesStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// Resolve the scheduling intent once and fall back to a readable default
@@ -43,7 +43,7 @@ func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.Exter
api.CapacityProbeIntent,
}
if slices.Contains(intentsExpectedToNotHaveImageMeta, intent) {
- traceLog.Debug("skipping filter: expected to have no image metadata",
+ traceLog.V(1).Info("skipping filter: expected to have no image metadata",
"intent", intent)
return result, nil
}
@@ -55,8 +55,7 @@ func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.Exter
if err != nil {
// Hypervisor type could not be determined. In this case, just
// ignore the filter and return all hosts.
- traceLog.Warn("could not determine hypervisor type from image properties",
- "error", err)
+ traceLog.Error(err, "could not determine hypervisor type from image properties")
// Expose this event through the step monitor to alert on high-frequency
// occurrences of this situation. Include the request intent as a dynamic
// label so the alert can pinpoint which scheduling intents are affected.
@@ -68,16 +67,16 @@ func (s *FilterImagePropertiesStep) Run(traceLog *slog.Logger, request api.Exter
}
if hvType != api.NovaImageMetaHVTypeKVM {
- traceLog.Info("filtering out all known kvm hypervisors since image properties indicate a different hypervisor type",
+ traceLog.V(1).Info("filtering out all known kvm hypervisors since image properties indicate a different hypervisor type",
"image_hypervisor_type", hvType)
hvs := new(hv1.HypervisorList)
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
for _, hv := range hvs.Items {
delete(result.Activations, hv.Name)
- traceLog.Debug("filtering host which is kvm hypervisor", "host", hv.Name)
+ traceLog.V(1).Info("filtering host which is kvm hypervisor", "host", hv.Name)
}
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go b/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go
index 8490b2b76..0c836a9e2 100644
--- a/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_image_properties_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -137,7 +137,7 @@ func TestFilterImagePropertiesStep_Run(t *testing.T) {
WithScheme(scheme).
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go
index 8c9045e34..a233bd37a 100644
--- a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go
+++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -18,7 +18,7 @@ type FilterInstanceGroupAffinityStep struct {
// Select hosts in spec.instance_group.
func (s *FilterInstanceGroupAffinityStep) Run(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
request api.ExternalSchedulerRequest,
) (*lib.FilterWeigherPipelineStepResult, error) {
@@ -35,28 +35,28 @@ func (s *FilterInstanceGroupAffinityStep) Run(
ig := request.Spec.Data.InstanceGroup
if ig == nil {
- traceLog.Info("no instance group in request, skipping filter")
+ traceLog.V(1).Info("no instance group in request, skipping filter")
return result, nil
}
policy := ig.Data.Policy
if policy != "affinity" {
- traceLog.Info("instance group policy is not 'affinity', skipping filter")
+ traceLog.V(1).Info("instance group policy is not 'affinity', skipping filter")
return result, nil
}
if len(ig.Data.Hosts) == 0 {
// Nothing to do.
- traceLog.Info("instance group has no hosts, skipping filter")
+ traceLog.V(1).Info("instance group has no hosts, skipping filter")
return result, nil
}
for host := range result.Activations {
if slices.Contains(ig.Data.Hosts, host) {
- traceLog.Info("host is in instance group, keeping", "host", host)
+ traceLog.V(1).Info("host is in instance group, keeping", "host", host)
continue
}
delete(result.Activations, host)
- traceLog.Info("filtered out host not in instance group", "host", host)
+ traceLog.V(1).Info("filtered out host not in instance group", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go
index e6e6a8803..5a955d515 100644
--- a/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_affinity_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -326,7 +326,7 @@ func TestFilterInstanceGroupAffinityStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
step := &FilterInstanceGroupAffinityStep{}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -364,7 +364,7 @@ func TestFilterInstanceGroupAffinityStep_SkipsForNonPlacementIntent(t *testing.T
request.Spec.Data.SchedulerHints = map[string]any{"_nova_check_type": intent}
step := &FilterInstanceGroupAffinityStep{}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go
index 7cf3ebbfd..ad17d8ab2 100644
--- a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go
+++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -21,7 +21,7 @@ type FilterInstanceGroupAntiAffinityStep struct {
// Select hosts not in spec_obj.instance_group but only until
// max_server_per_host is reached (by default = 1).
func (s *FilterInstanceGroupAntiAffinityStep) Run(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
request api.ExternalSchedulerRequest,
) (*lib.FilterWeigherPipelineStepResult, error) {
@@ -38,18 +38,18 @@ func (s *FilterInstanceGroupAntiAffinityStep) Run(
ig := request.Spec.Data.InstanceGroup
if ig == nil {
- traceLog.Info("no instance group in request, skipping filter")
+ traceLog.V(1).Info("no instance group in request, skipping filter")
return result, nil
}
policy := ig.Data.Policy
if policy != "anti-affinity" {
- traceLog.Info("instance group policy is not 'anti-affinity', skipping filter")
+ traceLog.V(1).Info("instance group policy is not 'anti-affinity', skipping filter")
return result, nil
}
memberVMs := ig.Data.Members
if len(memberVMs) == 0 {
// Nothing to do.
- traceLog.Info("instance group has no members, skipping filter")
+ traceLog.V(1).Info("instance group has no members, skipping filter")
return result, nil
}
maxServersPerHost := 1
@@ -63,7 +63,7 @@ func (s *FilterInstanceGroupAntiAffinityStep) Run(
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsByName := make(map[string]hv1.Hypervisor)
@@ -77,7 +77,7 @@ func (s *FilterInstanceGroupAntiAffinityStep) Run(
for host := range result.Activations {
hv, ok := hvsByName[host]
if !ok {
- traceLog.Error("hypervisor not found for host", "host", host)
+ traceLog.Error(nil, "hypervisor not found for host", "host", host)
delete(result.Activations, host)
continue
}
@@ -86,7 +86,7 @@ func (s *FilterInstanceGroupAntiAffinityStep) Run(
if slices.ContainsFunc(hv.Status.Instances, func(inst hv1.Instance) bool {
return inst.ID == request.Spec.Data.InstanceUUID
}) {
- traceLog.Info("host is running the same VM, not filtering out", "host", host)
+ traceLog.V(1).Info("host is running the same VM, not filtering out", "host", host)
continue
}
// Check how many instances from the group are already on this host.
@@ -98,7 +98,7 @@ func (s *FilterInstanceGroupAntiAffinityStep) Run(
}
if len(instancesInGroup) >= maxServersPerHost {
delete(result.Activations, host)
- traceLog.Info(
+ traceLog.V(1).Info(
"filtered out host exceeding max_server_per_host for instance group",
"host", host,
"instances_in_group", instancesInGroup,
diff --git a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go
index 63a9aff4a..bfbd1c475 100644
--- a/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_instance_group_anti_affinity_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -521,7 +521,7 @@ func TestFilterInstanceGroupAntiAffinityStep_Run(t *testing.T) {
WithScheme(scheme).
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@@ -576,7 +576,7 @@ func TestFilterInstanceGroupAntiAffinityStep_SkipsForNonPlacementIntent(t *testi
}
request.Spec.Data.SchedulerHints = map[string]any{"_nova_check_type": intent}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_live_migratable.go b/internal/scheduling/nova/plugins/filters/filter_live_migratable.go
index a19238721..94c525f53 100644
--- a/internal/scheduling/nova/plugins/filters/filter_live_migratable.go
+++ b/internal/scheduling/nova/plugins/filters/filter_live_migratable.go
@@ -6,7 +6,7 @@ package filters
import (
"context"
"errors"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -49,7 +49,7 @@ func (s *FilterLiveMigratableStep) checkHasSufficientFeatures(
// Ensure the target host of a live migration can accept the migrating VM.
func (s *FilterLiveMigratableStep) Run(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
request api.ExternalSchedulerRequest,
) (*lib.FilterWeigherPipelineStepResult, error) {
@@ -57,24 +57,24 @@ func (s *FilterLiveMigratableStep) Run(
intent, err := request.GetIntent()
if err != nil {
- traceLog.Info("failed to determine request intent, skipping filter", "error", err)
+ traceLog.V(1).Info("failed to determine request intent, skipping filter", "error", err)
return result, nil
}
if intent != api.LiveMigrationIntent {
- traceLog.Info("not a live migration request, skipping filter")
+ traceLog.V(1).Info("not a live migration request, skipping filter")
return result, nil
}
sourceHost, err := request.Spec.Data.GetSchedulerHintStr("source_host")
if err != nil || sourceHost == "" {
- traceLog.Info("no source_host scheduler hint, skipping filter")
+ traceLog.V(1).Info("no source_host scheduler hint, skipping filter")
//nolint:nilerr // Not an error we want to fail the scheduling for.
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsByName := make(map[string]hv1.Hypervisor)
@@ -85,26 +85,26 @@ func (s *FilterLiveMigratableStep) Run(
sourceHV, ok := hvsByName[sourceHost]
if !ok {
err := errors.New("source host hypervisor not found")
- traceLog.Error("failed to find source host hypervisor", "host", sourceHost, "error", err)
+ traceLog.Error(err, "failed to find source host hypervisor", "host", sourceHost)
return nil, err
}
for host := range result.Activations {
targetHV, ok := hvsByName[host]
if !ok {
- traceLog.Error("hypervisor not found for host", "host", host)
+ traceLog.Error(nil, "hypervisor not found for host", "host", host)
delete(result.Activations, host)
continue
}
if err := s.checkHasSufficientFeatures(sourceHV, targetHV); err != nil {
delete(result.Activations, host)
- traceLog.Info(
+ traceLog.V(1).Info(
"filtered out host not suitable for live migration",
"host", host,
"reason", err.Error(),
)
continue
}
- traceLog.Info("host is suitable for live migration, keeping", "host", host)
+ traceLog.V(1).Info("host is suitable for live migration, keeping", "host", host)
}
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go b/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go
index c5651b025..8f866b309 100644
--- a/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_live_migratable_test.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -641,7 +641,7 @@ func TestFilterLiveMigratableStep_Run(t *testing.T) {
},
}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if tt.expectErr {
if err == nil {
@@ -728,7 +728,7 @@ func TestFilterLiveMigratableStep_Run_SourceHostNotFound(t *testing.T) {
},
}
- _, err := step.Run(slog.Default(), request)
+ _, err := step.Run(logr.Discard(), request)
if err == nil {
t.Errorf("expected error when source host not found, got none")
}
@@ -774,7 +774,7 @@ func TestFilterLiveMigratableStep_Run_ClientError(t *testing.T) {
},
}
- _, err := step.Run(slog.Default(), request)
+ _, err := step.Run(logr.Discard(), request)
if err == nil {
t.Errorf("expected error when client fails, got none")
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_quota_enforcement.go b/internal/scheduling/nova/plugins/filters/filter_quota_enforcement.go
index 9f84d80cb..86d56e405 100644
--- a/internal/scheduling/nova/plugins/filters/filter_quota_enforcement.go
+++ b/internal/scheduling/nova/plugins/filters/filter_quota_enforcement.go
@@ -6,7 +6,7 @@ package filters
import (
"context"
"fmt"
- "log/slog"
+ "github.com/go-logr/logr"
"math"
"time"
@@ -72,7 +72,7 @@ type FilterQuotaEnforcement struct {
lib.BaseFilter[api.ExternalSchedulerRequest, FilterQuotaEnforcementOpts]
}
-func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterQuotaEnforcement) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
mode := "shadow"
@@ -89,19 +89,19 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
if err == nil {
switch intent {
case api.EvacuateIntent, api.LiveMigrationIntent:
- traceLog.Info("skipping quota enforcement for non-consuming intent", "intent", intent)
+ traceLog.V(1).Info("skipping quota enforcement for non-consuming intent", "intent", intent)
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped_intent_migration", "", "", "")
return result, nil
case api.ReserveForFailoverIntent, api.ReuseFailoverReservationIntent:
- traceLog.Info("skipping quota enforcement for failover reservation intent")
+ traceLog.V(1).Info("skipping quota enforcement for failover reservation intent")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped_intent_internal", "", "", "")
return result, nil
case api.ReserveForCommittedResourceIntent:
- traceLog.Info("skipping quota enforcement for committed resource reservation intent")
+ traceLog.V(1).Info("skipping quota enforcement for committed resource reservation intent")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped_intent_internal", "", "", "")
return result, nil
case api.CapacityProbeIntent:
- traceLog.Info("skipping quota enforcement for capacity probe intent")
+ traceLog.V(1).Info("skipping quota enforcement for capacity probe intent")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped", "", "", "")
return result, nil
}
@@ -115,17 +115,17 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
hwVersion := request.Spec.Data.Flavor.Data.ExtraSpecs["quota:hw_version"]
if projectID == "" {
- traceLog.Warn("no project ID in request, skipping quota enforcement")
+ traceLog.V(1).Info("no project ID in request, skipping quota enforcement")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped_missing_data", "", az, hwVersion)
return result, nil
}
if az == "" {
- traceLog.Warn("no availability zone in request, skipping quota enforcement")
+ traceLog.V(1).Info("no availability zone in request, skipping quota enforcement")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped_missing_data", "", "", hwVersion)
return result, nil
}
if hwVersion == "" {
- traceLog.Warn("no hw_version in flavor extra specs, skipping quota enforcement")
+ traceLog.V(1).Info("no hw_version in flavor extra specs, skipping quota enforcement")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_skipped_missing_data", "", az, "")
return result, nil
}
@@ -147,7 +147,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
resourceCores := "hw_version_" + hwVersion + "_cores"
resourceInstances := "hw_version_" + hwVersion + "_instances"
- traceLog.Info("quota enforcement check",
+ traceLog.V(1).Info("quota enforcement check",
"projectID", projectID,
"az", az,
"hwVersion", hwVersion,
@@ -161,7 +161,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
// (bypassing PAYG checks for cores/instances).
var crList v1alpha1.CommittedResourceList
if err := s.Client.List(context.Background(), &crList); err != nil {
- traceLog.Error("failed to list CommittedResources", "error", err)
+ traceLog.Error(err, "failed to list CommittedResources")
return nil, err
}
@@ -217,7 +217,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
}
freeGiB := amountGiB - usedGiB
if freeGiB >= requestRAM {
- traceLog.Info("CR headroom found",
+ traceLog.V(1).Info("CR headroom found",
"cr", cr.Name,
"amountGiB", amountGiB,
"usedGiB", usedGiB,
@@ -230,7 +230,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
}
if crHasHeadroom {
- traceLog.Info("quota enforcement ACCEPT: CR headroom available")
+ traceLog.V(1).Info("quota enforcement ACCEPT: CR headroom available")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_cr", "ram", az, hwVersion)
return result, nil
}
@@ -242,12 +242,12 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
pqName := fmt.Sprintf("quota-%s-%s", projectID, az)
if err := s.Client.Get(context.Background(), types.NamespacedName{Name: pqName}, &projectQuota); err != nil {
if apierrors.IsNotFound(err) {
- traceLog.Info("no ProjectQuota CRD found for project+AZ, skipping enforcement",
+ traceLog.V(1).Info("no ProjectQuota CRD found for project+AZ, skipping enforcement",
"projectID", projectID, "az", az)
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_no_quota", "", az, hwVersion)
return result, nil
}
- traceLog.Error("failed to get ProjectQuota", "name", pqName, "error", err)
+ traceLog.Error(err, "failed to get ProjectQuota", "name", pqName)
return nil, err
}
@@ -283,7 +283,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
headroom := quota - check.crDeduct - paygUsage
- traceLog.Info("PAYG headroom calculation",
+ traceLog.V(1).Info("PAYG headroom calculation",
"resource", check.name,
"quota", quota,
"crDeduct", check.crDeduct,
@@ -295,7 +295,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
if headroom < check.request {
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "reject", check.label, az, hwVersion)
if !s.Options.Enforce {
- traceLog.Info("quota enforcement SHADOW: would reject but enforce=false",
+ traceLog.V(1).Info("quota enforcement SHADOW: would reject but enforce=false",
"projectID", projectID,
"az", az,
"flavorGroup", hwVersion,
@@ -305,7 +305,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
)
return result, nil
}
- traceLog.Info("quota enforcement REJECT: no PAYG headroom",
+ traceLog.V(1).Info("quota enforcement REJECT: no PAYG headroom",
"projectID", projectID,
"az", az,
"flavorGroup", hwVersion,
@@ -320,7 +320,7 @@ func (s *FilterQuotaEnforcement) Run(traceLog *slog.Logger, request api.External
}
}
- traceLog.Info("quota enforcement ACCEPT: PAYG headroom available")
+ traceLog.V(1).Info("quota enforcement ACCEPT: PAYG headroom available")
QuotaEnforcementMetricsSingleton.RecordDecision(mode, "accept_payg", "", az, hwVersion)
return result, nil
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_metrics.go b/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_metrics.go
index 0743b6346..3586fae75 100644
--- a/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_metrics.go
+++ b/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_metrics.go
@@ -4,12 +4,16 @@
package filters
import (
- "log/slog"
"sync"
"github.com/prometheus/client_golang/prometheus"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+// log is the package-level logger for non-trace (global) log messages emitted
+// by the nova filters package.
+var log = logf.Log.WithName("filters")
+
// QuotaEnforcementMetrics holds Prometheus metrics for the quota-enforcement
// nova filter. The filter runs in either "enforce" mode (rejects when no
// headroom) or "shadow" mode (logs/counts what it would have rejected without
@@ -86,7 +90,7 @@ var recordDecisionNilOnce = &sync.Once{}
func (m *QuotaEnforcementMetrics) RecordDecision(mode, decision, resource, az, flavorGroup string) {
if m == nil || m.Decisions == nil {
recordDecisionNilOnce.Do(func() {
- slog.Warn("QuotaEnforcementMetrics is nil; decision metric not recorded "+
+ log.V(1).Info("QuotaEnforcementMetrics is nil; decision metric not recorded "+
"(is QuotaEnforcementMetricsSingleton initialized in cmd/manager?)",
"mode", mode,
"decision", decision,
diff --git a/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_test.go b/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_test.go
index 2960cc71b..ec1695651 100644
--- a/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_quota_enforcement_test.go
@@ -5,13 +5,13 @@ package filters
import (
"bytes"
- "log/slog"
"strings"
"sync"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"k8s.io/apimachinery/pkg/api/resource"
@@ -19,6 +19,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
)
func makeQuotaEnforcementRequest(projectID, az, hwVersion string, memoryMB, numInstances uint64, hints map[string]any) api.ExternalSchedulerRequest {
@@ -924,7 +926,7 @@ func TestFilterQuotaEnforcement_Run(t *testing.T) {
filter.Client = fakeClient
filter.Options.Enforce = tt.enforce
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -999,7 +1001,7 @@ func TestFilterQuotaEnforcement_DefaultIsShadow(t *testing.T) {
}
request := makeQuotaEnforcementRequest("project-1", "az-1", "hana_v2", 10240, 1, nil)
- result, err := filter.Run(slog.Default(), request)
+ result, err := filter.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -1063,7 +1065,7 @@ func TestNewQuotaEnforcementMetrics(t *testing.T) {
}
func TestQuotaEnforcementMetrics_RecordDecision_NilWarns(t *testing.T) {
- // Capture slog output to confirm the nil-receiver path warns exactly once,
+ // Capture log output to confirm the nil-receiver path warns exactly once,
// even when called many times. We freshly arm the package-level sync.Once
// so this test deterministically covers the warn path regardless of test
// ordering.
@@ -1072,9 +1074,7 @@ func TestQuotaEnforcementMetrics_RecordDecision_NilWarns(t *testing.T) {
t.Cleanup(func() { recordDecisionNilOnce = origOnce })
var buf bytes.Buffer
- origDefault := slog.Default()
- slog.SetDefault(slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})))
- t.Cleanup(func() { slog.SetDefault(origDefault) })
+ logf.SetLogger(zap.New(zap.WriteTo(&buf), zap.UseDevMode(true)))
var nilMetrics *QuotaEnforcementMetrics
// Must not panic, and must warn exactly once across multiple calls.
@@ -1112,7 +1112,7 @@ func TestFilterQuotaEnforcement_SkipsForNonPlacementIntent(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = FilterQuotaEnforcementOpts{Enforce: true}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_requested_destination.go b/internal/scheduling/nova/plugins/filters/filter_requested_destination.go
index 71cd48844..e4fa2af5d 100644
--- a/internal/scheduling/nova/plugins/filters/filter_requested_destination.go
+++ b/internal/scheduling/nova/plugins/filters/filter_requested_destination.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"slices"
"strings"
@@ -25,7 +25,7 @@ type FilterRequestedDestinationStep struct {
// OR logic, meaning the host only needs to match ONE of the UUIDs in that group.
// Example: ["agg1", "agg2,agg3"] means host must be in agg1 AND (agg2 OR agg3).
func (s *FilterRequestedDestinationStep) processRequestedAggregates(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
aggregates []string,
hvsByName map[string]hv1.Hypervisor,
activations map[string]float64,
@@ -38,7 +38,7 @@ func (s *FilterRequestedDestinationStep) processRequestedAggregates(
hv, exists := hvsByName[host]
if !exists {
delete(activations, host)
- traceLog.Info("filtered out host not in requested_destination aggregates (unknown host)", "host", host)
+ traceLog.V(1).Info("filtered out host not in requested_destination aggregates (unknown host)", "host", host)
continue
}
hvAggregateUUIDs := make([]string, 0, len(hv.Status.Aggregates))
@@ -64,36 +64,36 @@ func (s *FilterRequestedDestinationStep) processRequestedAggregates(
}
if !allMatch {
delete(activations, host)
- traceLog.Info(
+ traceLog.V(1).Info(
"filtered out host not in requested_destination aggregates",
"host", host, "hostAggregates", hvAggregateUUIDs,
"requestedAggregates", aggregates,
)
continue
}
- traceLog.Info("host is in requested_destination aggregates, keeping", "host", host)
+ traceLog.V(1).Info("host is in requested_destination aggregates, keeping", "host", host)
}
}
// processRequestedHost filters hosts based on the requested specific host.
// It removes all hosts except the one matching the requested hostname.
func (s *FilterRequestedDestinationStep) processRequestedHost(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
host string,
activations map[string]float64,
) {
if host == "" {
- traceLog.Info("no specific host in requested_destination, skipping host filtering")
+ traceLog.V(1).Info("no specific host in requested_destination, skipping host filtering")
return
}
for h := range activations {
if h != host {
delete(activations, h)
- traceLog.Info("filtered out host not matching requested_destination host", "host", h)
+ traceLog.V(1).Info("filtered out host not matching requested_destination host", "host", h)
continue
}
- traceLog.Info("host matches requested_destination host, keeping", "host", h)
+ traceLog.V(1).Info("host matches requested_destination host, keeping", "host", h)
}
}
@@ -101,7 +101,7 @@ func (s *FilterRequestedDestinationStep) processRequestedHost(
// The requested destination can include a specific host, aggregates, or both.
// When both are specified, aggregate filtering is applied first, followed by
// host filtering.
-func (s *FilterRequestedDestinationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterRequestedDestinationStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// The requested_destination hint is only set by Nova for user-directed placement;
if intent, err := request.GetIntent(); err == nil && slices.Contains([]v1alpha1.SchedulingIntent{
@@ -114,16 +114,16 @@ func (s *FilterRequestedDestinationStep) Run(traceLog *slog.Logger, request api.
}
rd := request.Spec.Data.RequestedDestination
if rd == nil {
- traceLog.Info("no requested_destination in request, skipping filter")
+ traceLog.V(1).Info("no requested_destination in request, skipping filter")
return result, nil
}
if len(rd.Data.Aggregates) == 0 && rd.Data.Host == "" {
- traceLog.Info("requested_destination has no host or aggregates, skipping filter")
+ traceLog.V(1).Info("requested_destination has no host or aggregates, skipping filter")
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsByName := make(map[string]hv1.Hypervisor)
diff --git a/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go b/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go
index fab4c7aec..a145190b9 100644
--- a/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_requested_destination_test.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -578,7 +578,7 @@ func TestFilterRequestedDestinationStep_Run(t *testing.T) {
},
}
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if tt.expectErr {
if err == nil {
@@ -684,7 +684,7 @@ func TestFilterRequestedDestinationStep_processRequestedAggregates(t *testing.T)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
step := &FilterRequestedDestinationStep{}
- step.processRequestedAggregates(slog.Default(), tt.aggregates, tt.hypervisors, tt.activations)
+ step.processRequestedAggregates(logr.Discard(), tt.aggregates, tt.hypervisors, tt.activations)
if len(tt.activations) != len(tt.expectedHosts) {
t.Errorf("expected %d hosts, got %d", len(tt.expectedHosts), len(tt.activations))
}
@@ -726,7 +726,7 @@ func TestFilterRequestedDestinationStep_processRequestedHost(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
step := &FilterRequestedDestinationStep{}
- step.processRequestedHost(slog.Default(), tt.host, tt.activations)
+ step.processRequestedHost(logr.Discard(), tt.host, tt.activations)
if len(tt.activations) != len(tt.expectedHosts) {
t.Errorf("expected %d hosts, got %d", len(tt.expectedHosts), len(tt.activations))
}
@@ -777,7 +777,7 @@ func TestFilterRequestedDestinationStep_Run_ClientError(t *testing.T) {
},
}
- _, err := step.Run(slog.Default(), request)
+ _, err := step.Run(logr.Discard(), request)
if err == nil {
t.Errorf("expected error when client fails, got none")
}
@@ -809,7 +809,7 @@ func TestFilterRequestedDestinationStep_SkipsForNonPlacementIntent(t *testing.T)
},
Hosts: []api.ExternalSchedulerHost{{ComputeHost: "host1"}, {ComputeHost: "host2"}},
}
- result, err := step.Run(slog.Default(), request)
+ result, err := step.Run(logr.Discard(), request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/filters/filter_status_conditions.go b/internal/scheduling/nova/plugins/filters/filter_status_conditions.go
index 60b616d71..310a238c2 100644
--- a/internal/scheduling/nova/plugins/filters/filter_status_conditions.go
+++ b/internal/scheduling/nova/plugins/filters/filter_status_conditions.go
@@ -5,7 +5,7 @@ package filters
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
@@ -20,12 +20,12 @@ type FilterStatusConditionsStep struct {
// Check that all status conditions meet the expected values, for example,
// that the hypervisor is ready and not disabled.
-func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *FilterStatusConditionsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
@@ -50,7 +50,7 @@ func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.Exte
for conditionType, expectedStatus := range expected {
cd := meta.FindStatusCondition(hv.Status.Conditions, conditionType)
if cd == nil {
- traceLog.Info(
+ traceLog.V(1).Info(
"hypervisor missing condition, keeping",
"host", hv.Name, "condition", conditionType,
)
@@ -62,7 +62,7 @@ func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.Exte
continue // Don't care about this condition
}
if cd.Status != expectedStatus {
- traceLog.Info(
+ traceLog.V(1).Info(
"hypervisor condition not met, filtering host",
"host", hv.Name,
"condition", conditionType,
@@ -73,12 +73,12 @@ func (s *FilterStatusConditionsStep) Run(traceLog *slog.Logger, request api.Exte
}
}
if allMet {
- traceLog.Info("hypervisor meets all status conditions, keeping host", "host", hv.Name)
+ traceLog.V(1).Info("hypervisor meets all status conditions, keeping host", "host", hv.Name)
hostsReady[hv.Name] = struct{}{}
}
}
- traceLog.Info("hosts passing status conditions filter", "hosts", hostsReady)
+ traceLog.V(1).Info("hosts passing status conditions filter", "hosts", hostsReady)
for host := range result.Activations {
if _, ok := hostsReady[host]; ok {
continue
diff --git a/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go b/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go
index 7b35de033..7862b6544 100644
--- a/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go
+++ b/internal/scheduling/nova/plugins/filters/filter_status_conditions_test.go
@@ -4,7 +4,7 @@
package filters
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -343,7 +343,7 @@ func TestFilterStatusConditionsStep_Run(t *testing.T) {
WithScheme(scheme).
WithObjects(hvs...).
Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_binpack.go b/internal/scheduling/nova/plugins/weighers/kvm_binpack.go
index 32ef49a97..a314dc5e8 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_binpack.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_binpack.go
@@ -7,12 +7,12 @@ import (
"context"
"errors"
"fmt"
- "log/slog"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
+ "github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/resource"
)
@@ -69,13 +69,13 @@ type KVMBinpackStep struct {
}
// Run this weigher in the pipeline after filters have been executed.
-func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *KVMBinpackStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["binpack score"] = s.PrepareStats(request, "float")
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsByName := make(map[string]hv1.Hypervisor, len(hvs.Items))
@@ -90,7 +90,7 @@ func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedule
for host := range result.Activations {
hv, ok := hvsByName[host]
if !ok {
- traceLog.Warn("no hv for host, skipping", "host", host)
+ traceLog.V(1).Info("no hv for host, skipping", "host", host)
continue
}
var totalWeightedUtilization, totalWeight float64
@@ -99,24 +99,24 @@ func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedule
// Effective capacity = capacity * overcommit ratio.
capacity, ok := hv.Status.EffectiveCapacity[resourceName]
if !ok {
- traceLog.Warn("no effective capacity in status, skipping",
+ traceLog.V(1).Info("no effective capacity in status, skipping",
"host", host, "resource", resourceName)
continue
}
if capacity.IsZero() {
- traceLog.Warn("effective capacity is zero, skipping",
+ traceLog.V(1).Info("effective capacity is zero, skipping",
"host", host, "resource", resourceName)
continue
}
allocation, ok := hv.Status.Allocation[resourceName]
if !ok {
- traceLog.Warn("no allocation in status, skipping",
+ traceLog.V(1).Info("no allocation in status, skipping",
"host", host, "resource", resourceName)
continue
}
vmReq, ok := vmResources[resourceName]
if !ok {
- traceLog.Warn("no resource request for vm, skipping",
+ traceLog.V(1).Info("no resource request for vm, skipping",
"resource", resourceName)
continue
}
@@ -133,7 +133,7 @@ func (s *KVMBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedule
}
result.Activations[host] = score
result.Statistics["binpack score"].Hosts[host] = score
- traceLog.Info("calculated binpack score for host",
+ traceLog.V(1).Info("calculated binpack score for host",
"host", host, "score", score)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go b/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go
index 810581ac8..421b53042 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_binpack_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"strings"
"testing"
@@ -448,7 +448,7 @@ func TestKVMBinpackStep_Run(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation.go b/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation.go
index 684900dc0..fc0c8c6b9 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation.go
@@ -5,9 +5,9 @@ package weighers
import (
"context"
- "log/slog"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
+ "github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/resource"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -52,13 +52,13 @@ type KVMCommittedResourceReservationStep struct {
// Run the weigher step.
// Hosts with a matching CommittedResourceReservation (project + flavor group + free capacity) get a higher weight.
-func (s *KVMCommittedResourceReservationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *KVMCommittedResourceReservationStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
// Skip for failover reservation scheduling — CR reservations should not influence
// where failover capacity is placed.
if intent, err := request.GetIntent(); err == nil && intent == api.ReserveForFailoverIntent {
- traceLog.Info("skipping committed resource reservation weigher for failover reservation intent")
+ traceLog.V(1).Info("skipping committed resource reservation weigher for failover reservation intent")
return result, nil
}
@@ -66,7 +66,7 @@ func (s *KVMCommittedResourceReservationStep) Run(traceLog *slog.Logger, request
az := request.Spec.Data.AvailabilityZone
resourceGroup, err := request.Spec.Data.GetSchedulerHintStr(api.HintKeyResourceGroup)
if err != nil || resourceGroup == "" {
- traceLog.Info("skipping committed resource reservation weigher: no resource group in request")
+ traceLog.V(1).Info("skipping committed resource reservation weigher: no resource group in request")
return result, nil //nolint:nilerr
}
@@ -103,7 +103,7 @@ func (s *KVMCommittedResourceReservationStep) Run(traceLog *slog.Logger, request
}
if freeMemoryBytes(res) >= flavorMemoryBytes {
reservationHosts[res.Status.Host] = true
- traceLog.Info("found committed resource reservation with free capacity",
+ traceLog.V(1).Info("found committed resource reservation with free capacity",
"reservation", res.Name,
"host", res.Status.Host,
"project", projectID,
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation_test.go
index 1318fa67f..421c6b75b 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_committed_resource_reservation_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -240,7 +240,7 @@ func TestKVMCommittedResourceReservationStep_Run(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot.go b/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot.go
index 56f09651f..9c897a61c 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot.go
@@ -5,8 +5,8 @@ package weighers
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -62,7 +62,7 @@ type KVMCRMigrationSlotStep struct {
}
func (s *KVMCRMigrationSlotStep) Run(
- traceLog *slog.Logger,
+ traceLog logr.Logger,
request api.ExternalSchedulerRequest,
) (*lib.FilterWeigherPipelineStepResult, error) {
@@ -70,7 +70,7 @@ func (s *KVMCRMigrationSlotStep) Run(
intent, err := request.GetIntent()
if err != nil || intent != api.LiveMigrationIntent {
- traceLog.Info("not a live migration, skipping CR migration slot weigher")
+ traceLog.V(1).Info("not a live migration, skipping CR migration slot weigher")
return result, nil //nolint:nilerr
}
@@ -101,14 +101,14 @@ func (s *KVMCRMigrationSlotStep) Run(
}
if sourceSlot == nil {
- traceLog.Info("migrating VM has no confirmed CR reservation slot, skipping slot weigher",
+ traceLog.V(1).Info("migrating VM has no confirmed CR reservation slot, skipping slot weigher",
"instanceUUID", instanceUUID)
return result, nil
}
slotMemoryBytes := sourceSlot.Spec.Resources[hv1.ResourceMemory]
if slotMemoryBytes.IsZero() {
- traceLog.Info("source CR slot has no memory resource, skipping slot weigher",
+ traceLog.V(1).Info("source CR slot has no memory resource, skipping slot weigher",
"instanceUUID", instanceUUID,
"reservation", sourceSlot.Name)
return result, nil
@@ -116,7 +116,7 @@ func (s *KVMCRMigrationSlotStep) Run(
resourceGroup := sourceSlot.Spec.CommittedResourceReservation.ResourceGroup
- traceLog.Info("found source CR reservation slot for migrating VM",
+ traceLog.V(1).Info("found source CR reservation slot for migrating VM",
"instanceUUID", instanceUUID,
"reservation", sourceSlot.Name,
"slotMemoryBytes", slotMemoryBytes.Value(),
@@ -137,15 +137,15 @@ func (s *KVMCRMigrationSlotStep) Run(
switch {
case hasSlot:
result.Activations[host] = slotHostWeight
- traceLog.Info("host has existing CR slot for migration, boosting weight",
+ traceLog.V(1).Info("host has existing CR slot for migration, boosting weight",
"host", host, "weight", slotHostWeight)
case canFit:
result.Activations[host] = slotHostWeight
- traceLog.Info("host can accommodate slot via reconciler, boosting weight",
+ traceLog.V(1).Info("host can accommodate slot via reconciler, boosting weight",
"host", host, "weight", slotHostWeight)
default:
result.Activations[host] = defaultHostWeight
- traceLog.Info("host cannot accommodate CR slot, applying low weight",
+ traceLog.V(1).Info("host cannot accommodate CR slot, applying low weight",
"host", host, "weight", defaultHostWeight)
}
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot_test.go b/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot_test.go
index 4ff8d3f21..3156c6699 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -235,7 +235,7 @@ func TestKVMCRMigrationSlotStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
weigher := newCRMigrationSlotWeigher(t, tt.opts, tt.objects...)
- result, err := weigher.Run(slog.Default(), tt.request)
+ result, err := weigher.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go
index dcbcbf8bd..3088496b0 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation.go
@@ -5,7 +5,8 @@ package weighers
import (
"context"
- "log/slog"
+
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -50,12 +51,12 @@ type KVMFailoverEvacuationStep struct {
// Run the weigher step.
// For evacuation requests, hosts matching a failover reservation where the VM is in Allocations get a higher weight.
// For non-evacuation requests (e.g., live migration, rebuild), this weigher has no effect.
-func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *KVMFailoverEvacuationStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
intent, err := request.GetIntent()
if err != nil || intent != api.EvacuateIntent {
- traceLog.Info("skipping failover weigher for non-evacuation request")
+ traceLog.V(1).Info("skipping failover weigher for non-evacuation request")
return result, nil //nolint:nilerr // intentionally skip weigher on error
}
@@ -85,7 +86,7 @@ func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.Exter
if reservation.Status.FailoverReservation != nil {
if _, ok := reservation.Status.FailoverReservation.Allocations[instanceUUID]; ok {
failoverHosts[reservation.Status.Host] = true
- traceLog.Info("found failover reservation for VM",
+ traceLog.V(1).Info("found failover reservation for VM",
"reservation", reservation.Name,
"host", reservation.Status.Host,
"instanceUUID", instanceUUID)
@@ -99,7 +100,7 @@ func (s *KVMFailoverEvacuationStep) Run(traceLog *slog.Logger, request api.Exter
for _, host := range request.Hosts {
if failoverHosts[host.ComputeHost] {
result.Activations[host.ComputeHost] = failoverWeight
- traceLog.Info("assigning failover weight to host",
+ traceLog.V(1).Info("assigning failover weight to host",
"host", host.ComputeHost,
"weight", failoverWeight,
"instanceUUID", instanceUUID)
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go
index 1aee73b06..16299100b 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_evacuation_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -263,7 +263,7 @@ func TestKVMFailoverEvacuationStep_Run(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go
index 727afce33..270d3c34c 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation.go
@@ -6,7 +6,8 @@ package weighers
import (
"context"
"errors"
- "log/slog"
+
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -78,12 +79,12 @@ type KVMFailoverReservationConsolidationStep struct {
// Run the weigher step.
// For reserve_for_failover requests, hosts are scored based on existing failover reservation density
// and same-spec diversity. For all other request types, this weigher has no effect.
-func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *KVMFailoverReservationConsolidationStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
intent, err := request.GetIntent()
if err != nil || intent != api.ReserveForFailoverIntent {
- traceLog.Info("skipping failover reservation consolidation weigher for non-failover-reservation request")
+ traceLog.V(1).Info("skipping failover reservation consolidation weigher for non-failover-reservation request")
return result, nil //nolint:nilerr // intentionally skip weigher on error
}
@@ -129,7 +130,7 @@ func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, req
// If there are no failover reservations, the weigher has no information to act on.
if totalReservations == 0 {
- traceLog.Info("no active failover reservations found, skipping consolidation weigher")
+ traceLog.V(1).Info("no active failover reservations found, skipping consolidation weigher")
return result, nil
}
@@ -145,7 +146,7 @@ func (s *KVMFailoverReservationConsolidationStep) Run(traceLog *slog.Logger, req
score := (totalCountWeight/t)*hostTotal - (sameSpecPenalty/t)*hostSameGroup
result.Activations[host.ComputeHost] = score
- traceLog.Info("calculated failover consolidation score for host",
+ traceLog.V(1).Info("calculated failover consolidation score for host",
"host", host.ComputeHost,
"totalOnHost", hostTotal,
"sameGroupOnHost", hostSameGroup,
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go
index 2ba65b436..44736e6a4 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_failover_reservation_consolidation_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"math"
"testing"
@@ -255,7 +255,7 @@ func TestKVMFailoverReservationConsolidationStep_Run(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go
index 6085edb70..c95ff1b3c 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity.go
@@ -5,12 +5,12 @@ package weighers
import (
"context"
- "log/slog"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
+ "github.com/go-logr/logr"
)
// This weigher implements the "soft affinity" and "soft anti-affinity" policy
@@ -26,13 +26,13 @@ type KVMInstanceGroupSoftAffinityStep struct {
lib.BaseWeigher[api.ExternalSchedulerRequest, lib.EmptyFilterWeigherPipelineStepOpts]
}
-func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["affinity"] = s.PrepareStats(request, "float")
ig := request.Spec.Data.InstanceGroup
if ig == nil {
- traceLog.Info("no instance group in request, skipping weigher")
+ traceLog.V(1).Info("no instance group in request, skipping weigher")
return result, nil
}
policy := ig.Data.Policy
@@ -43,17 +43,17 @@ func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request ap
case "soft-affinity":
factor = 1.0
default:
- traceLog.Info("instance group policy is not 'soft-affinity' or 'soft-anti-affinity', skipping weigher", "policy", policy)
+ traceLog.V(1).Info("instance group policy is not 'soft-affinity' or 'soft-anti-affinity', skipping weigher", "policy", policy)
return result, nil
}
if len(ig.Data.Members) == 0 {
- traceLog.Info("instance group has no members, skipping weigher")
+ traceLog.V(1).Info("instance group has no members, skipping weigher")
return result, nil
}
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsByName := make(map[string]hv1.Hypervisor, len(hvs.Items))
@@ -67,7 +67,7 @@ func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request ap
for host := range result.Activations {
hv, ok := hvsByName[host]
if !ok {
- traceLog.Info("host not found in hypervisor list, skipping", "host", host)
+ traceLog.V(1).Info("host not found in hypervisor list, skipping", "host", host)
continue
}
count := 0
@@ -79,7 +79,7 @@ func (s *KVMInstanceGroupSoftAffinityStep) Run(traceLog *slog.Logger, request ap
weight := factor * float64(count)
result.Activations[host] = weight
result.Statistics["affinity"].Hosts[host] = weight
- traceLog.Info("calculated affinity weight for host",
+ traceLog.V(1).Info("calculated affinity weight for host",
"host", host, "count", count, "weight", weight)
}
return result, nil
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go
index fcf13f86b..b839388b2 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_instance_group_soft_affinity_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -346,7 +346,7 @@ func TestKVMInstanceGroupSoftAffinityStep_Run(t *testing.T) {
step := &KVMInstanceGroupSoftAffinityStep{}
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go
index b65a5f75f..52632df77 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts.go
@@ -7,12 +7,12 @@ import (
"context"
"errors"
"fmt"
- "log/slog"
"slices"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
+ "github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/resource"
)
@@ -57,13 +57,13 @@ type KVMPreferSmallerHostsStep struct {
}
// Run this weigher in the pipeline after filters have been executed.
-func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *KVMPreferSmallerHostsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["small host score"] = s.PrepareStats(request, "float")
hvs := &hv1.HypervisorList{}
if err := s.Client.List(context.Background(), hvs); err != nil {
- traceLog.Error("failed to list hypervisors", "error", err)
+ traceLog.Error(err, "failed to list hypervisors")
return nil, err
}
hvsByName := make(map[string]hv1.Hypervisor, len(hvs.Items))
@@ -84,7 +84,7 @@ func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.Exter
// Effective capacity = capacity * overcommit ratio.
capacity, ok := hv.Status.EffectiveCapacity[resourceName]
if !ok {
- traceLog.Warn("hypervisor has no effective capacity for resource, skipping",
+ traceLog.V(1).Info("hypervisor has no effective capacity for resource, skipping",
"host", hv.Name, "resource", resourceName)
continue
}
@@ -100,7 +100,7 @@ func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.Exter
for host := range result.Activations {
hv, ok := hvsByName[host]
if !ok {
- traceLog.Warn("no hv for host, skipping", "host", host)
+ traceLog.V(1).Info("no hv for host, skipping", "host", host)
continue
}
@@ -109,7 +109,7 @@ func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.Exter
for resourceName, weight := range s.Options.ResourceWeights {
capacity, ok := hv.Status.EffectiveCapacity[resourceName]
if !ok {
- traceLog.Warn("hypervisor has no effective capacity for resource, skipping",
+ traceLog.V(1).Info("hypervisor has no effective capacity for resource, skipping",
"host", hv.Name, "resource", resourceName)
continue
}
@@ -118,14 +118,14 @@ func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.Exter
largestCap := largest[resourceName]
if smallestCap == nil || largestCap == nil {
- traceLog.Warn("no effective capacity range found for resource, skipping",
+ traceLog.V(1).Info("no effective capacity range found for resource, skipping",
"resource", resourceName)
continue
}
// If all hosts have the same effective capacity for this resource, skip it
if smallestCap.Cmp(*largestCap) == 0 {
- traceLog.Info("all hypervisors have the same effective capacity for resource, skipping",
+ traceLog.V(1).Info("all hypervisors have the same effective capacity for resource, skipping",
"resource", resourceName)
continue
}
@@ -147,7 +147,7 @@ func (s *KVMPreferSmallerHostsStep) Run(traceLog *slog.Logger, request api.Exter
}
result.Activations[host] = score
result.Statistics["small host score"].Hosts[host] = score
- traceLog.Info("calculated small host score for host",
+ traceLog.V(1).Info("calculated small host score for host",
"host", host, "score", score)
}
diff --git a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go
index 2ab2deb89..1f5a07677 100644
--- a/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go
+++ b/internal/scheduling/nova/plugins/weighers/kvm_prefer_smaller_hosts_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"strings"
"testing"
@@ -599,7 +599,7 @@ func TestKVMPreferSmallerHostsStep_Run(t *testing.T) {
step.Client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
step.Options = tt.opts
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go
index b2b886d49..426526e2d 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects.go
@@ -6,7 +6,8 @@ package weighers
import (
"context"
"errors"
- "log/slog"
+
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -52,7 +53,7 @@ func (s *VMwareAntiAffinityNoisyProjectsStep) Init(ctx context.Context, client c
}
// Downvote the hosts a project is currently running on if it's noisy.
-func (s *VMwareAntiAffinityNoisyProjectsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *VMwareAntiAffinityNoisyProjectsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["avg cpu usage of this project"] = s.PrepareStats(request, "%")
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go
index 2d5d1c389..5005bae45 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_anti_affinity_noisy_projects_test.go
@@ -5,7 +5,7 @@ package weighers
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"strings"
"testing"
@@ -272,7 +272,7 @@ func TestVMwareAntiAffinityNoisyProjectsStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go
index 14905cf00..a12c35fe1 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts.go
@@ -6,7 +6,8 @@ package weighers
import (
"context"
"errors"
- "log/slog"
+
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -61,7 +62,7 @@ func (s *VMwareAvoidLongTermContendedHostsStep) Init(ctx context.Context, client
}
// Downvote hosts that are highly contended.
-func (s *VMwareAvoidLongTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *VMwareAvoidLongTermContendedHostsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%")
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go
index 0b690d853..701cef92e 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_long_term_contended_hosts_test.go
@@ -5,7 +5,7 @@ package weighers
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"strings"
"testing"
@@ -255,7 +255,7 @@ func TestVMwareAvoidLongTermContendedHostsStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go
index fd9e81335..209eb97ba 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts.go
@@ -6,7 +6,8 @@ package weighers
import (
"context"
"errors"
- "log/slog"
+
+ "github.com/go-logr/logr"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
@@ -61,7 +62,7 @@ func (s *VMwareAvoidShortTermContendedHostsStep) Init(ctx context.Context, clien
}
// Downvote hosts that are highly contended.
-func (s *VMwareAvoidShortTermContendedHostsStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *VMwareAvoidShortTermContendedHostsStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["avg cpu contention"] = s.PrepareStats(request, "%")
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go
index 895bee459..624ee7e14 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_avoid_short_term_contended_hosts_test.go
@@ -5,7 +5,7 @@ package weighers
import (
"context"
- "log/slog"
+ "github.com/go-logr/logr"
"strings"
"testing"
@@ -255,7 +255,7 @@ func TestVMwareAvoidShortTermContendedHostsStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_binpack.go b/internal/scheduling/nova/plugins/weighers/vmware_binpack.go
index 5edd24e12..3f8b01fd8 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_binpack.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_binpack.go
@@ -7,9 +7,10 @@ import (
"context"
"errors"
"fmt"
- "log/slog"
"slices"
+ "github.com/go-logr/logr"
+
api "github.com/cobaltcore-dev/cortex/api/external/nova"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/knowledge/extractor/plugins/compute"
@@ -85,7 +86,7 @@ func (s *VMwareBinpackStep) Init(ctx context.Context, client client.Client, weig
}
// Run this weigher in the pipeline after filters have been executed.
-func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *VMwareBinpackStep) Run(traceLog logr.Logger, request api.ExternalSchedulerRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
result.Statistics["binpack score"] = s.PrepareStats(request, "float")
@@ -111,7 +112,7 @@ func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSched
for host := range result.Activations {
utilization, ok := utilizationByHost[host]
if !ok {
- traceLog.Warn("no utilization for host, skipping", "host", host)
+ traceLog.V(1).Info("no utilization for host, skipping", "host", host)
continue
}
allocations := s.calcHostAllocation(utilization)
@@ -122,7 +123,7 @@ func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSched
for resourceName, weight := range s.Options.ResourceWeights {
capacity, ok := capacities[resourceName]
if !ok {
- traceLog.Warn("no capacity for resource on host, skipping resource in score calculation",
+ traceLog.V(1).Info("no capacity for resource on host, skipping resource in score calculation",
"host", host, "resource", resourceName)
continue
}
@@ -131,13 +132,13 @@ func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSched
}
allocation, ok := allocations[resourceName]
if !ok {
- traceLog.Warn("no allocation for resource on host, skipping resource in score calculation",
+ traceLog.V(1).Info("no allocation for resource on host, skipping resource in score calculation",
"host", host, "resource", resourceName)
continue
}
vmReq, ok := vmResources[resourceName]
if !ok {
- traceLog.Warn("no resource request for vm, skipping",
+ traceLog.V(1).Info("no resource request for vm, skipping",
"resource", resourceName)
continue
}
@@ -154,7 +155,7 @@ func (s *VMwareBinpackStep) Run(traceLog *slog.Logger, request api.ExternalSched
}
result.Activations[host] = score
result.Statistics["binpack score"].Hosts[host] = score
- traceLog.Info("calculated binpack score for host",
+ traceLog.V(1).Info("calculated binpack score for host",
"host", host, "score", score)
}
diff --git a/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go b/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go
index 515379dd0..404722f43 100644
--- a/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go
+++ b/internal/scheduling/nova/plugins/weighers/vmware_binpack_test.go
@@ -4,7 +4,7 @@
package weighers
import (
- "log/slog"
+ "github.com/go-logr/logr"
"testing"
api "github.com/cobaltcore-dev/cortex/api/external/nova"
@@ -211,7 +211,7 @@ func TestVMwareBinpackStep_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result, err := step.Run(slog.Default(), tt.request)
+ result, err := step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/pods/filter_weigher_pipeline_controller.go b/internal/scheduling/pods/filter_weigher_pipeline_controller.go
index 8619e4cb5..50afde197 100644
--- a/internal/scheduling/pods/filter_weigher_pipeline_controller.go
+++ b/internal/scheduling/pods/filter_weigher_pipeline_controller.go
@@ -134,7 +134,7 @@ func (c *FilterWeigherPipelineController) process(ctx context.Context, decision
return err
}
if pod.Spec.NodeName != "" {
- log.Info("pod is already assigned to a node", "node", pod.Spec.NodeName)
+ log.V(1).Info("pod is already assigned to a node", "node", pod.Spec.NodeName)
return nil
}
diff --git a/internal/scheduling/pods/plugins/filters/filter_node_affinity.go b/internal/scheduling/pods/plugins/filters/filter_node_affinity.go
index 996897bdb..e6ffafeeb 100644
--- a/internal/scheduling/pods/plugins/filters/filter_node_affinity.go
+++ b/internal/scheduling/pods/plugins/filters/filter_node_affinity.go
@@ -5,12 +5,12 @@ package filters
import (
"context"
- "log/slog"
"strconv"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -27,7 +27,7 @@ func (f *NodeAffinityFilter) Validate(ctx context.Context, params v1alpha1.Param
return nil
}
-func (NodeAffinityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (NodeAffinityFilter) Run(traceLog logr.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
activations := make(map[string]float64)
stats := make(map[string]lib.FilterWeigherPipelineStepStatistics)
diff --git a/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go b/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go
index 0172d60d7..e27c74931 100644
--- a/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go
+++ b/internal/scheduling/pods/plugins/filters/filter_node_affinity_test.go
@@ -4,11 +4,11 @@
package filters
import (
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -399,7 +399,7 @@ func TestNodeAffinityFilter_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &NodeAffinityFilter{}
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Errorf("expected Run() to succeed, got error: %v", err)
diff --git a/internal/scheduling/pods/plugins/filters/filter_node_available.go b/internal/scheduling/pods/plugins/filters/filter_node_available.go
index 2d6e11d22..857848fc5 100644
--- a/internal/scheduling/pods/plugins/filters/filter_node_available.go
+++ b/internal/scheduling/pods/plugins/filters/filter_node_available.go
@@ -5,11 +5,11 @@ package filters
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -26,7 +26,7 @@ func (f *NodeAvailableFilter) Validate(ctx context.Context, params v1alpha1.Para
return nil
}
-func (NodeAvailableFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (NodeAvailableFilter) Run(traceLog logr.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
activations := make(map[string]float64)
stats := make(map[string]lib.FilterWeigherPipelineStepStatistics)
diff --git a/internal/scheduling/pods/plugins/filters/filter_node_available_test.go b/internal/scheduling/pods/plugins/filters/filter_node_available_test.go
index 3eac7873c..a872ee81a 100644
--- a/internal/scheduling/pods/plugins/filters/filter_node_available_test.go
+++ b/internal/scheduling/pods/plugins/filters/filter_node_available_test.go
@@ -4,11 +4,11 @@
package filters
import (
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -309,7 +309,7 @@ func TestNodeAvailableFilter_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &NodeAvailableFilter{}
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Errorf("expected Run() to succeed, got error: %v", err)
diff --git a/internal/scheduling/pods/plugins/filters/filter_node_capacity.go b/internal/scheduling/pods/plugins/filters/filter_node_capacity.go
index cfceb8835..03ff0cda5 100644
--- a/internal/scheduling/pods/plugins/filters/filter_node_capacity.go
+++ b/internal/scheduling/pods/plugins/filters/filter_node_capacity.go
@@ -5,12 +5,12 @@ package filters
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
"github.com/cobaltcore-dev/cortex/internal/scheduling/pods/helpers"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -27,7 +27,7 @@ func (f *NodeCapacityFilter) Validate(ctx context.Context, params v1alpha1.Param
return nil
}
-func (NodeCapacityFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (NodeCapacityFilter) Run(traceLog logr.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
activations := make(map[string]float64)
stats := make(map[string]lib.FilterWeigherPipelineStepStatistics)
diff --git a/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go b/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go
index 543b4561d..434b78898 100644
--- a/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go
+++ b/internal/scheduling/pods/plugins/filters/filter_node_capacity_test.go
@@ -4,11 +4,11 @@
package filters
import (
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -351,7 +351,7 @@ func TestNodeCapacityFilter_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &NodeCapacityFilter{}
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Errorf("expected Run() to succeed, got error: %v", err)
diff --git a/internal/scheduling/pods/plugins/filters/filter_noop.go b/internal/scheduling/pods/plugins/filters/filter_noop.go
index e0666537b..d1be45d8d 100644
--- a/internal/scheduling/pods/plugins/filters/filter_noop.go
+++ b/internal/scheduling/pods/plugins/filters/filter_noop.go
@@ -5,11 +5,11 @@ package filters
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -31,7 +31,7 @@ func (f *NoopFilter) Validate(ctx context.Context, params v1alpha1.Parameters) e
// not in the map are considered as filtered out.
// Provide a traceLog that contains the global request id and should
// be used to log the step's execution.
-func (NoopFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (NoopFilter) Run(traceLog logr.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
activations := make(map[string]float64, len(request.Nodes))
stats := make(map[string]lib.FilterWeigherPipelineStepStatistics)
// Usually you would do some filtering here, or adjust the weights.
diff --git a/internal/scheduling/pods/plugins/filters/filter_noop_test.go b/internal/scheduling/pods/plugins/filters/filter_noop_test.go
index dcdd90a69..8fb269da0 100644
--- a/internal/scheduling/pods/plugins/filters/filter_noop_test.go
+++ b/internal/scheduling/pods/plugins/filters/filter_noop_test.go
@@ -4,11 +4,11 @@
package filters
import (
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -85,7 +85,7 @@ func TestNoopFilter_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &NoopFilter{}
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Errorf("expected Run() to succeed, got error: %v", err)
diff --git a/internal/scheduling/pods/plugins/filters/filter_taint.go b/internal/scheduling/pods/plugins/filters/filter_taint.go
index 5a54d1cb6..81b19d880 100644
--- a/internal/scheduling/pods/plugins/filters/filter_taint.go
+++ b/internal/scheduling/pods/plugins/filters/filter_taint.go
@@ -5,11 +5,11 @@ package filters
import (
"context"
- "log/slog"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@@ -26,7 +26,7 @@ func (f *TaintFilter) Validate(ctx context.Context, params v1alpha1.Parameters)
return nil
}
-func (TaintFilter) Run(traceLog *slog.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (TaintFilter) Run(traceLog logr.Logger, request pods.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
activations := make(map[string]float64)
stats := make(map[string]lib.FilterWeigherPipelineStepStatistics)
diff --git a/internal/scheduling/pods/plugins/filters/filter_taint_test.go b/internal/scheduling/pods/plugins/filters/filter_taint_test.go
index 1d248b685..677b57ce9 100644
--- a/internal/scheduling/pods/plugins/filters/filter_taint_test.go
+++ b/internal/scheduling/pods/plugins/filters/filter_taint_test.go
@@ -4,11 +4,11 @@
package filters
import (
- "log/slog"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/api/v1alpha1"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -252,7 +252,7 @@ func TestTaintFilter_Run(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := &TaintFilter{}
- result, err := filter.Run(slog.Default(), tt.request)
+ result, err := filter.Run(logr.Discard(), tt.request)
if err != nil {
t.Errorf("expected Run() to succeed, got error: %v", err)
diff --git a/internal/scheduling/pods/plugins/weighers/binpack.go b/internal/scheduling/pods/plugins/weighers/binpack.go
index 07d310fb8..845726441 100644
--- a/internal/scheduling/pods/plugins/weighers/binpack.go
+++ b/internal/scheduling/pods/plugins/weighers/binpack.go
@@ -5,12 +5,12 @@ package weighers
import (
"errors"
- "log/slog"
"math"
api "github.com/cobaltcore-dev/cortex/api/external/pods"
"github.com/cobaltcore-dev/cortex/internal/scheduling/lib"
"github.com/cobaltcore-dev/cortex/internal/scheduling/pods/helpers"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
)
@@ -31,7 +31,7 @@ type BinpackingStep struct {
lib.BaseWeigher[api.PodPipelineRequest, BinpackingStepOpts]
}
-func (s *BinpackingStep) Run(traceLog *slog.Logger, request api.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
+func (s *BinpackingStep) Run(traceLog logr.Logger, request api.PodPipelineRequest) (*lib.FilterWeigherPipelineStepResult, error) {
result := s.IncludeAllHostsFromRequest(request)
podResources := helpers.GetPodResourceRequests(request.Pod)
diff --git a/internal/scheduling/pods/plugins/weighers/binpack_test.go b/internal/scheduling/pods/plugins/weighers/binpack_test.go
index 198e110c1..fe99b486b 100644
--- a/internal/scheduling/pods/plugins/weighers/binpack_test.go
+++ b/internal/scheduling/pods/plugins/weighers/binpack_test.go
@@ -4,11 +4,11 @@
package weighers
import (
- "log/slog"
"math"
"testing"
"github.com/cobaltcore-dev/cortex/api/external/pods"
+ "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -257,7 +257,7 @@ func TestBinpackingStep_Run(t *testing.T) {
},
}
- result, err := tt.step.Run(slog.Default(), tt.request)
+ result, err := tt.step.Run(logr.Discard(), tt.request)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
diff --git a/internal/scheduling/reservations/commitments/api/change_commitments.go b/internal/scheduling/reservations/commitments/api/change_commitments.go
index 1a1b601d2..e4c04d70a 100644
--- a/internal/scheduling/reservations/commitments/api/change_commitments.go
+++ b/internal/scheduling/reservations/commitments/api/change_commitments.go
@@ -307,7 +307,7 @@ ProcessLoop:
isDelete := commitment.NewStatus.IsNone()
crName := "commitment-" + string(commitment.UUID)
- logger.Info("processing commitment",
+ logger.V(1).Info("processing commitment",
"commitmentUUID", commitment.UUID,
"oldStatus", commitment.OldStatus.UnwrapOr("none"),
"newStatus", commitment.NewStatus.UnwrapOr("none"),
@@ -335,7 +335,7 @@ ProcessLoop:
// The create side will update ProjectID in place via CreateOrUpdate; no deletion
// needed here. Phase 3 of ApplyCommitmentState will replace any reservation slots
// whose ProjectID no longer matches.
- logger.Info("commitment transfer: skipping deletion, will update in place",
+ logger.V(1).Info("commitment transfer: skipping deletion, will update in place",
"commitmentUUID", commitment.UUID)
continue
}
@@ -424,7 +424,7 @@ ProcessLoop:
return nil
}
- logger.Info("CommittedResource CRDs written, polling for controller outcome", "count", len(toWatch))
+ logger.V(1).Info("CommittedResource CRDs written, polling for controller outcome", "count", len(toWatch))
watchStart := time.Now()
rejected, watchErrs := watchCRsUntilReady(
@@ -565,10 +565,10 @@ func watchCRsUntilReady(
switch {
case cond.Status == metav1.ConditionTrue:
- logger.Info("CommittedResource accepted", "name", name)
+ logger.V(1).Info("CommittedResource accepted", "name", name)
delete(pending, name)
case cond.Status == metav1.ConditionFalse && cond.Reason == v1alpha1.CommittedResourceReasonPlanned:
- logger.Info("CommittedResource planned (will reserve at activation)", "name", name)
+ logger.V(1).Info("CommittedResource planned (will reserve at activation)", "name", name)
delete(pending, name) // planned = accepted; controller will reserve at activation
case cond.Status == metav1.ConditionFalse && cond.Reason == v1alpha1.CommittedResourceReasonRejected:
logger.Info("CommittedResource rejected", "name", name, "reason", cond.Message)
diff --git a/internal/scheduling/reservations/commitments/api/info.go b/internal/scheduling/reservations/commitments/api/info.go
index 2fe7325e4..7ab5bcac8 100644
--- a/internal/scheduling/reservations/commitments/api/info.go
+++ b/internal/scheduling/reservations/commitments/api/info.go
@@ -223,7 +223,7 @@ func (api *HTTPAPI) buildServiceInfo(ctx context.Context, logger logr.Logger) (l
version = knowledgeCRD.Status.LastContentChange.Unix()
}
- logger.Info("built service info",
+ logger.V(1).Info("built service info",
"resourceCount", len(resources),
"version", version)
diff --git a/internal/scheduling/reservations/commitments/api/quota.go b/internal/scheduling/reservations/commitments/api/quota.go
index 0e6f97730..59d759a9a 100644
--- a/internal/scheduling/reservations/commitments/api/quota.go
+++ b/internal/scheduling/reservations/commitments/api/quota.go
@@ -51,7 +51,7 @@ func (api *HTTPAPI) HandleQuota(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Request-ID", requestID)
log := apiLog.WithValues("requestID", requestID, "endpoint", "quota")
- log.Info("received quota request", "method", r.Method, "path", r.URL.Path)
+ log.V(1).Info("received quota request", "method", r.Method, "path", r.URL.Path)
if r.Method != http.MethodPut {
api.quotaError(w, http.StatusMethodNotAllowed, "Method not allowed", startTime)
diff --git a/internal/scheduling/reservations/commitments/api/report_capacity.go b/internal/scheduling/reservations/commitments/api/report_capacity.go
index 4eb91b34c..dc25fb547 100644
--- a/internal/scheduling/reservations/commitments/api/report_capacity.go
+++ b/internal/scheduling/reservations/commitments/api/report_capacity.go
@@ -89,7 +89,7 @@ func (api *HTTPAPI) HandleReportCapacity(w http.ResponseWriter, r *http.Request)
return
}
- logger.Info("calculated capacity report", "resourceCount", len(report.Resources))
+ logger.V(1).Info("calculated capacity report", "resourceCount", len(report.Resources))
// Update capacity gauge for each resource/AZ combination.
for resName, resReport := range report.Resources {
diff --git a/internal/scheduling/reservations/commitments/api/report_usage.go b/internal/scheduling/reservations/commitments/api/report_usage.go
index 0bbb31321..e1c78a09e 100644
--- a/internal/scheduling/reservations/commitments/api/report_usage.go
+++ b/internal/scheduling/reservations/commitments/api/report_usage.go
@@ -34,7 +34,7 @@ func (api *HTTPAPI) HandleReportUsage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Request-ID", requestID)
log := apiLog.WithValues("requestID", requestID, "endpoint", "report-usage")
- log.Info("received report-usage request", "method", r.Method, "path", r.URL.Path)
+ log.V(1).Info("received report-usage request", "method", r.Method, "path", r.URL.Path)
// Check if API is enabled
if !api.config.EnableReportUsage {
@@ -84,7 +84,7 @@ func (api *HTTPAPI) HandleReportUsage(w http.ResponseWriter, r *http.Request) {
return
}
- log.Info("report-usage request completed", "projectID", projectID, "duration", time.Since(startTime).String())
+ log.V(1).Info("report-usage request completed", "projectID", projectID, "duration", time.Since(startTime).String())
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
diff --git a/internal/scheduling/reservations/commitments/committed_resource_controller.go b/internal/scheduling/reservations/commitments/committed_resource_controller.go
index b3a438090..e5101f0d2 100644
--- a/internal/scheduling/reservations/commitments/committed_resource_controller.go
+++ b/internal/scheduling/reservations/commitments/committed_resource_controller.go
@@ -92,7 +92,7 @@ func (r *CommittedResourceController) Reconcile(ctx context.Context, req ctrl.Re
// can report the outcome back to Limes. If AllowRejection=false (syncer path), the controller
// retries with exponential backoff — Limes does not require confirmation for these transitions.
func (r *CommittedResourceController) reconcilePending(ctx context.Context, logger logr.Logger, cr *v1alpha1.CommittedResource) (ctrl.Result, error) {
- logger.Info("reconciling pending resource",
+ logger.V(1).Info("reconciling pending resource",
"generation", cr.Generation,
"az", cr.Spec.AvailabilityZone,
"amount", cr.Spec.Amount.String(),
@@ -120,7 +120,7 @@ func (r *CommittedResourceController) reconcilePending(ctx context.Context, logg
delay := r.retryDelay(cr)
logger.Error(applyErr, "pending commitment placement failed, will retry", "requeueAfter", delay)
if setErr := r.setNotReadyRetry(ctx, cr, applyErr.Error()); setErr != nil {
- logger.Info("status update failed after placement error, will be corrected on next reconcile", "err", setErr)
+ logger.V(1).Info("status update failed after placement error, will be corrected on next reconcile", "err", setErr)
}
// Return nil so controller-runtime honours RequeueAfter. Returning the status-update
// error would cause controller-runtime to ignore RequeueAfter and use exponential backoff.
@@ -146,7 +146,7 @@ func (r *CommittedResourceController) reconcilePending(ctx context.Context, logg
delay := r.retryDelay(cr)
logger.Info("pending commitment placement failed, will retry", "reason", failReason, "firstSlotReason", firstSlotReason, "slotsReady", readySlots, "slotsTotal", result.TotalSlots, "failedSlots", failedSlots, "requeueAfter", delay)
if setErr := r.setNotReadyRetry(ctx, cr, failReason); setErr != nil {
- logger.Info("status update failed after placement failure, will be corrected on next reconcile", "err", setErr)
+ logger.V(1).Info("status update failed after placement failure, will be corrected on next reconcile", "err", setErr)
}
// Return nil so controller-runtime honours RequeueAfter. Returning the status-update
// error would cause controller-runtime to ignore RequeueAfter and use exponential backoff.
@@ -180,7 +180,7 @@ func (r *CommittedResourceController) reconcilePending(ctx context.Context, logg
return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
}
if latest.Generation > initialGen {
- logger.Info("generation advanced during reconcile, requeueing",
+ logger.V(1).Info("generation advanced during reconcile, requeueing",
"processedGen", initialGen,
"latestGen", latest.Generation,
)
@@ -190,7 +190,7 @@ func (r *CommittedResourceController) reconcilePending(ctx context.Context, logg
}
func (r *CommittedResourceController) reconcileCommitted(ctx context.Context, logger logr.Logger, cr *v1alpha1.CommittedResource) (ctrl.Result, error) {
- logger.Info("reconciling committed resource",
+ logger.V(1).Info("reconciling committed resource",
"generation", cr.Generation,
"az", cr.Spec.AvailabilityZone,
"amount", cr.Spec.Amount.String(),
@@ -225,7 +225,7 @@ func (r *CommittedResourceController) reconcileCommitted(ctx context.Context, lo
return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
}
if latest.Generation > initialGen {
- logger.Info("generation advanced during reconcile, requeueing",
+ logger.V(1).Info("generation advanced during reconcile, requeueing",
"processedGen", initialGen,
"latestGen", latest.Generation,
)
@@ -261,7 +261,7 @@ func (r *CommittedResourceController) reconcileCommitted(ctx context.Context, lo
delay := r.retryDelay(cr)
logger.Error(applyErr, "committed placement incomplete, will retry", "requeueAfter", delay)
if setErr := r.setNotReadyRetry(ctx, cr, applyErr.Error()); setErr != nil {
- logger.Info("status update failed after placement error, will be corrected on next reconcile", "err", setErr)
+ logger.V(1).Info("status update failed after placement error, will be corrected on next reconcile", "err", setErr)
}
// Return nil so controller-runtime honours RequeueAfter. Returning the status-update
// error would cause controller-runtime to ignore RequeueAfter and use exponential backoff.
@@ -283,7 +283,7 @@ func (r *CommittedResourceController) reconcileCommitted(ctx context.Context, lo
delay := r.retryDelay(cr)
logger.Info("committed placement failed, will retry", "reason", failReason, "firstSlotReason", firstSlotReason, "slotsReady", readySlots, "slotsTotal", result.TotalSlots, "failedSlots", failedSlots, "requeueAfter", delay)
if setErr := r.setNotReadyRetry(ctx, cr, failReason); setErr != nil {
- logger.Info("status update failed after placement failure, will be corrected on next reconcile", "err", setErr)
+ logger.V(1).Info("status update failed after placement failure, will be corrected on next reconcile", "err", setErr)
}
return ctrl.Result{RequeueAfter: delay}, nil
}
@@ -318,7 +318,7 @@ func (r *CommittedResourceController) reconcileCommitted(ctx context.Context, lo
return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
}
if latest.Generation > initialGen {
- logger.Info("generation advanced during reconcile, requeueing",
+ logger.V(1).Info("generation advanced during reconcile, requeueing",
"processedGen", initialGen,
"latestGen", latest.Generation,
)
@@ -358,7 +358,7 @@ func (r *CommittedResourceController) reconcileCoresHeadroom(ctx context.Context
if cr.Spec.AllowRejection {
delay = 1 * time.Second
}
- logger.Info("FlavorGroupCapacity CRD not found, will retry", "requeueAfter", delay)
+ logger.V(1).Info("FlavorGroupCapacity CRD not found, will retry", "requeueAfter", delay)
return ctrl.Result{RequeueAfter: delay}, r.setNotReadyRetry(ctx, cr, "waiting for capacity data")
}
@@ -370,7 +370,7 @@ func (r *CommittedResourceController) reconcileCoresHeadroom(ctx context.Context
if cr.Spec.AllowRejection {
delay = 1 * time.Second
}
- logger.Info("CPU capacity not yet populated in FlavorGroupCapacity CRD, will retry", "requeueAfter", delay)
+ logger.V(1).Info("CPU capacity not yet populated in FlavorGroupCapacity CRD, will retry", "requeueAfter", delay)
return ctrl.Result{RequeueAfter: delay}, r.setNotReadyRetry(ctx, cr, "waiting for CPU capacity data")
}
// Key present (even if zero): the capacity controller has run and this is the actual capacity.
@@ -403,7 +403,7 @@ func (r *CommittedResourceController) reconcileCoresHeadroom(ctx context.Context
requestedCores := cr.Spec.Amount.Value()
headroom := totalCores - alreadyCommitted
- logger.Info("cores headroom check",
+ logger.V(1).Info("cores headroom check",
"total", totalCores,
"committed", alreadyCommitted,
"requested", requestedCores,
@@ -432,7 +432,7 @@ func (r *CommittedResourceController) reconcileCoresHeadroom(ctx context.Context
return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
}
if latest.Generation > initialGen {
- logger.Info("generation advanced during reconcile, requeueing",
+ logger.V(1).Info("generation advanced during reconcile, requeueing",
"processedGen", initialGen,
"latestGen", latest.Generation,
)
@@ -466,6 +466,7 @@ func (r *CommittedResourceController) applyReservationState(ctx context.Context,
EnablePaygPreAllocation: r.Conf.EnablePaygPreAllocation,
VMSource: r.VMSource,
NoHostsFoundTTL: r.Conf.NoHostsFoundTTL.Duration,
+ OwnerObject: cr,
})
result, err := mgr.ApplyCommitmentState(ctx, logger, state, flavorGroups, "committed-resource-controller")
if err != nil {
@@ -475,7 +476,7 @@ func (r *CommittedResourceController) applyReservationState(ctx context.Context,
}
return nil, err
}
- logger.Info("commitment state applied", "created", result.Created, "deleted", result.Deleted, "repaired", result.Repaired)
+ logger.V(1).Info("commitment state applied", "created", result.Created, "deleted", result.Deleted, "repaired", result.Repaired)
return result, nil
}
@@ -533,7 +534,7 @@ func (r *CommittedResourceController) checkChildReservationStatus(ctx context.Co
res.Spec.CommittedResourceReservation != nil &&
res.Spec.CommittedResourceReservation.ParentGeneration == cr.Generation {
nudgeVal := strconv.FormatInt(cr.Generation, 10) + "-" + strconv.FormatInt(time.Now().UnixMilli(), 10)
- ctrl.LoggerFrom(ctx).Info("nudging stale reservation: parentGen written to spec but echo pending",
+ ctrl.LoggerFrom(ctx).V(1).Info("nudging stale reservation: parentGen written to spec but echo pending",
"slot", res.Name,
"crGen", cr.Generation,
"obs", obs,
@@ -669,6 +670,7 @@ func (r *CommittedResourceController) rollbackToAccepted(ctx context.Context, lo
rollbackMgr := NewReservationManager(r.Client, ReservationManagerConfig{
EnablePaygPreAllocation: r.Conf.EnablePaygPreAllocation,
VMSource: r.VMSource,
+ OwnerObject: cr,
})
if _, err := rollbackMgr.ApplyCommitmentState(ctx, logger, state, flavorGroups, "committed-resource-controller-rollback"); err != nil {
return fmt.Errorf("rollback apply failed: %w", err)
diff --git a/internal/scheduling/reservations/commitments/config.go b/internal/scheduling/reservations/commitments/config.go
index c948ffa02..d82740b53 100644
--- a/internal/scheduling/reservations/commitments/config.go
+++ b/internal/scheduling/reservations/commitments/config.go
@@ -194,7 +194,7 @@ func LogFlavorGroupResourceConfig(log logr.Logger, cfg map[string]FlavorGroupRes
log.Info("WARNING: ramUnitGiB not configured for flavor group, defaulting to 1 GiB",
"flavorGroup", groupName)
} else {
- log.Info("flavor group RAM unit",
+ log.V(1).Info("flavor group RAM unit",
"flavorGroup", groupName, "ramUnitGiB", resCfg.RAM.RAMUnitGiB)
}
}
diff --git a/internal/scheduling/reservations/commitments/e2e_checks.go b/internal/scheduling/reservations/commitments/e2e_checks.go
index a736cc7ed..c03129774 100644
--- a/internal/scheduling/reservations/commitments/e2e_checks.go
+++ b/internal/scheduling/reservations/commitments/e2e_checks.go
@@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"io"
- "log/slog"
"net/http"
"strings"
"time"
@@ -18,8 +17,11 @@ import (
"github.com/sapcc/go-bits/must"
. "go.xyrillian.de/gg/option"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ ctrl "sigs.k8s.io/controller-runtime"
)
+var e2eLog = ctrl.Log.WithName("commitments-e2e").WithValues("module", "committed-resources")
+
const (
// Default URL for the commitments API endpoint.
// This should match the service name in the helm chart.
@@ -115,7 +117,7 @@ func e2eAZs(config E2EChecksConfig) []liquid.AvailabilityZone {
func CheckCommitmentsInfoEndpoint(ctx context.Context, config E2EChecksConfig) {
baseURL := e2eBaseURL(config)
apiURL := baseURL + "/commitments/v1/info"
- slog.Info("checking commitments info endpoint", "apiURL", apiURL)
+ e2eLog.V(1).Info("checking commitments info endpoint", "apiURL", apiURL)
httpReq := must.Return(http.NewRequestWithContext(ctx, http.MethodGet, apiURL, http.NoBody))
httpReq.Header.Set("Accept", "application/json")
@@ -135,9 +137,9 @@ func CheckCommitmentsInfoEndpoint(ctx context.Context, config E2EChecksConfig) {
}
if serviceInfo.Version < 0 {
- slog.Warn("commitments info returned version -1, knowledge may not be ready yet")
+ e2eLog.Info("commitments info returned version -1, knowledge may not be ready yet")
}
- slog.Info("commitments info endpoint check passed",
+ e2eLog.V(1).Info("commitments info endpoint check passed",
"version", serviceInfo.Version,
"resourceCount", len(serviceInfo.Resources),
)
@@ -168,7 +170,7 @@ func CheckCommitmentsRoundTrip(ctx context.Context, config E2EChecksConfig) {
}
if checked == 0 {
- slog.Warn("round-trip check: no HandlesCommitments resources found in /info — nothing checked")
+ e2eLog.Info("round-trip check: no HandlesCommitments resources found in /info — nothing checked")
}
}
@@ -208,7 +210,7 @@ func e2eRoundTripResource(
},
}
- slog.Info("round-trip check: creating test commitment",
+ e2eLog.V(1).Info("round-trip check: creating test commitment",
"resource", resourceName, "uuid", testUUID, "project", projectID, "az", az)
rejectionReason := e2eSendChangeCommitments(ctx, baseURL, createReq, requestTimeout)
@@ -219,16 +221,16 @@ func e2eRoundTripResource(
if !strings.Contains(rejectionReason, "not sufficient capacity") && !strings.Contains(rejectionReason, "insufficient CPU cores") {
panic(fmt.Sprintf("round-trip check: commitment rejected with unexpected reason for resource %s: %s", resourceName, rejectionReason))
}
- slog.Info("round-trip check: commitment rejected — no capacity, continuing",
+ e2eLog.Info("round-trip check: commitment rejected — no capacity, continuing",
"resource", resourceName, "reason", rejectionReason)
return
}
- slog.Info("round-trip check: commitment accepted", "resource", resourceName, "uuid", testUUID)
+ e2eLog.V(1).Info("round-trip check: commitment accepted", "resource", resourceName, "uuid", testUUID)
// Register cleanup immediately so it runs even if the usage check panics.
defer func() {
if noCleanup {
- slog.Info("round-trip check: NoCleanup=true, keeping commitment for inspection",
+ e2eLog.Info("round-trip check: NoCleanup=true, keeping commitment for inspection",
"resource", resourceName, "uuid", testUUID)
return
}
@@ -252,11 +254,11 @@ func e2eRoundTripResource(
},
},
}
- slog.Info("round-trip check: deleting test commitment", "resource", resourceName, "uuid", testUUID)
+ e2eLog.V(1).Info("round-trip check: deleting test commitment", "resource", resourceName, "uuid", testUUID)
if reason := e2eSendChangeCommitments(ctx, baseURL, deleteReq, requestTimeout); reason != "" {
panic(fmt.Sprintf("round-trip check: delete of test commitment %s was rejected: %s", testUUID, reason))
}
- slog.Info("round-trip check: commitment deleted", "resource", resourceName, "uuid", testUUID)
+ e2eLog.V(1).Info("round-trip check: commitment deleted", "resource", resourceName, "uuid", testUUID)
}()
report := e2eFetchUsageReport(ctx, baseURL, az, projectID)
@@ -291,7 +293,7 @@ func CheckCommitmentsMultiFlavorGroupBatch(ctx context.Context, config E2EChecks
}
if checked == 0 {
- slog.Warn("batch check: no HandlesCommitments resources found in /info — nothing checked")
+ e2eLog.Info("batch check: no HandlesCommitments resources found in /info — nothing checked")
}
}
@@ -339,12 +341,12 @@ func e2eBatchFlavorGroupResource(
},
}
- slog.Info("batch check: creating pending commitment",
+ e2eLog.V(1).Info("batch check: creating pending commitment",
"resource", resourceName, "uuid", uuidA, "project", projectID, "az", az)
if reason := e2eSendChangeCommitments(ctx, baseURL, req1, requestTimeout); reason != "" {
panic(fmt.Sprintf("batch check: unexpected rejection for pending creation of %s: %s", resourceName, reason))
}
- slog.Info("batch check: pending commitment accepted", "resource", resourceName, "uuid", uuidA)
+ e2eLog.V(1).Info("batch check: pending commitment accepted", "resource", resourceName, "uuid", uuidA)
// cleanup is updated as the state advances; the deferred call always runs the latest version.
var cleanup func()
@@ -373,11 +375,11 @@ func e2eBatchFlavorGroupResource(
},
},
}
- slog.Info("batch check: deleting pending commitment", "resource", resourceName, "uuid", uuidA)
+ e2eLog.V(1).Info("batch check: deleting pending commitment", "resource", resourceName, "uuid", uuidA)
if reason := e2eSendChangeCommitments(ctx, baseURL, req, requestTimeout); reason != "" {
panic(fmt.Sprintf("batch check: delete of pending commitment %s rejected: %s", uuidA, reason))
}
- slog.Info("batch check: pending commitment deleted", "resource", resourceName, "uuid", uuidA)
+ e2eLog.V(1).Info("batch check: pending commitment deleted", "resource", resourceName, "uuid", uuidA)
}
// Request 2: atomic bundle — UUID:A pending→confirmed (grown to amount=3), UUID:B new confirmed amount=1.
@@ -413,7 +415,7 @@ func e2eBatchFlavorGroupResource(
},
}
- slog.Info("batch check: sending batch confirmation",
+ e2eLog.V(1).Info("batch check: sending batch confirmation",
"resource", resourceName, "uuidA", uuidA, "uuidB", uuidB,
"totalConfirmed", confirmedAmountA+confirmedAmountB,
"project", projectID, "az", az)
@@ -422,17 +424,17 @@ func e2eBatchFlavorGroupResource(
if !strings.Contains(reason, "not sufficient capacity") && !strings.Contains(reason, "insufficient CPU cores") {
panic(fmt.Sprintf("batch check: unexpected rejection for batch of %s: %s", resourceName, reason))
}
- slog.Info("batch check: batch rejected — no capacity for full amount, cleanup will remove pending",
+ e2eLog.Info("batch check: batch rejected — no capacity for full amount, cleanup will remove pending",
"resource", resourceName, "reason", reason)
return
}
- slog.Info("batch check: batch confirmed", "resource", resourceName, "uuidA", uuidA, "uuidB", uuidB)
+ e2eLog.V(1).Info("batch check: batch confirmed", "resource", resourceName, "uuidA", uuidA, "uuidB", uuidB)
report := e2eFetchUsageReport(ctx, baseURL, az, projectID)
e2eLogUsageReport(report, az, projectID)
if noCleanup {
- slog.Info("batch check: NoCleanup=true, keeping commitments for inspection",
+ e2eLog.Info("batch check: NoCleanup=true, keeping commitments for inspection",
"resource", resourceName, "uuidA", uuidA, "uuidB", uuidB, "project", projectID)
cleanup = nil
return
@@ -470,11 +472,11 @@ func e2eBatchFlavorGroupResource(
},
},
}
- slog.Info("batch check: deleting confirmed commitments", "resource", resourceName, "uuidA", uuidA, "uuidB", uuidB)
+ e2eLog.V(1).Info("batch check: deleting confirmed commitments", "resource", resourceName, "uuidA", uuidA, "uuidB", uuidB)
if reason := e2eSendChangeCommitments(ctx, baseURL, req, requestTimeout); reason != "" {
panic(fmt.Sprintf("batch check: cleanup of confirmed commitments %s/%s rejected: %s", uuidA, uuidB, reason))
}
- slog.Info("batch check: confirmed commitments deleted", "resource", resourceName, "uuidA", uuidA, "uuidB", uuidB)
+ e2eLog.V(1).Info("batch check: confirmed commitments deleted", "resource", resourceName, "uuidA", uuidA, "uuidB", uuidB)
}
}
@@ -501,7 +503,7 @@ func CheckCommitmentsDryRun(ctx context.Context, config E2EChecksConfig) {
}
if checked == 0 {
- slog.Warn("dry-run check: no HandlesCommitments resources found in /info — nothing checked")
+ e2eLog.Info("dry-run check: no HandlesCommitments resources found in /info — nothing checked")
}
}
@@ -541,15 +543,15 @@ func e2eDryRunResource(
},
}
- slog.Info("dry-run check: sending dry-run request",
+ e2eLog.V(1).Info("dry-run check: sending dry-run request",
"resource", resourceName, "uuid", testUUID, "project", projectID, "az", az)
rejectionReason := e2eSendChangeCommitments(ctx, baseURL, req, requestTimeout)
switch {
case rejectionReason == "":
- slog.Info("dry-run check: accepted", "resource", resourceName, "az", az)
+ e2eLog.V(1).Info("dry-run check: accepted", "resource", resourceName, "az", az)
case strings.Contains(rejectionReason, "not sufficient capacity") || strings.Contains(rejectionReason, "insufficient CPU cores"):
- slog.Info("dry-run check: capacity rejection (expected in constrained clusters)",
+ e2eLog.Info("dry-run check: capacity rejection (expected in constrained clusters)",
"resource", resourceName, "az", az, "reason", rejectionReason)
default:
panic(fmt.Sprintf("dry-run check: unexpected rejection for resource %s in %s: %s", resourceName, az, rejectionReason))
@@ -603,7 +605,7 @@ func e2eLogUsageReport(report liquid.ServiceUsageReport, az liquid.AvailabilityZ
payg++
}
}
- slog.Info("usage report",
+ e2eLog.V(1).Info("usage report",
"project", projectID, "az", az, "resource", resourceName,
"usage", azReport.Usage,
"subresources", len(azReport.Subresources),
@@ -638,7 +640,7 @@ func e2eSendChangeCommitments(ctx context.Context, baseURL string, req liquid.Co
if err := json.Unmarshal(respBody, &result); err != nil {
panic(fmt.Sprintf("failed to decode change-commitments response (requestID=%s): %v", requestID, err))
}
- slog.Info("change-commitments response", "requestID", requestID, "rejected", result.RejectionReason != "")
+ e2eLog.V(1).Info("change-commitments response", "requestID", requestID, "rejected", result.RejectionReason != "")
return result.RejectionReason
}
@@ -696,7 +698,7 @@ func CheckCommitmentsMarketplaceTransfer(ctx context.Context, config E2EChecksCo
}
if checked == 0 {
- slog.Warn("transfer check: no HandlesCommitments resources found in /info — nothing checked")
+ e2eLog.Info("transfer check: no HandlesCommitments resources found in /info — nothing checked")
}
}
@@ -738,23 +740,23 @@ func e2eMarketplaceTransferResource(
},
}
- slog.Info("transfer check: creating commitment in projectA",
+ e2eLog.V(1).Info("transfer check: creating commitment in projectA",
"resource", resourceName, "uuid", testUUID, "projectA", projectA, "az", az)
if reason := e2eSendChangeCommitments(ctx, baseURL, createReq, requestTimeout); reason != "" {
if !strings.Contains(reason, "not sufficient capacity") && !strings.Contains(reason, "insufficient CPU cores") {
panic(fmt.Sprintf("transfer check: initial create rejected with unexpected reason for %s: %s", resourceName, reason))
}
- slog.Info("transfer check: initial create rejected — no capacity, skipping transfer check",
+ e2eLog.Info("transfer check: initial create rejected — no capacity, skipping transfer check",
"resource", resourceName, "reason", reason)
return
}
- slog.Info("transfer check: commitment accepted in projectA", "resource", resourceName, "uuid", testUUID)
+ e2eLog.V(1).Info("transfer check: commitment accepted in projectA", "resource", resourceName, "uuid", testUUID)
// cleanupProject tracks where the commitment lives; the deferred cleanup deletes from there.
cleanupProject := projectA
defer func() {
if noCleanup {
- slog.Info("transfer check: NoCleanup=true, keeping commitment for inspection",
+ e2eLog.Info("transfer check: NoCleanup=true, keeping commitment for inspection",
"resource", resourceName, "uuid", testUUID, "project", cleanupProject)
return
}
@@ -778,11 +780,11 @@ func e2eMarketplaceTransferResource(
},
},
}
- slog.Info("transfer check: deleting commitment", "resource", resourceName, "uuid", testUUID, "project", cleanupProject)
+ e2eLog.V(1).Info("transfer check: deleting commitment", "resource", resourceName, "uuid", testUUID, "project", cleanupProject)
if reason := e2eSendChangeCommitments(ctx, baseURL, deleteReq, requestTimeout); reason != "" {
panic(fmt.Sprintf("transfer check: cleanup of %s rejected: %s", testUUID, reason))
}
- slog.Info("transfer check: commitment deleted", "resource", resourceName, "uuid", testUUID)
+ e2eLog.V(1).Info("transfer check: commitment deleted", "resource", resourceName, "uuid", testUUID)
}()
// Step 2: transfer — delete UUID from projectA, create UUID in projectB, one atomic request.
@@ -820,21 +822,21 @@ func e2eMarketplaceTransferResource(
},
}
- slog.Info("transfer check: transferring commitment from projectA to projectB",
+ e2eLog.V(1).Info("transfer check: transferring commitment from projectA to projectB",
"resource", resourceName, "uuid", testUUID, "projectA", projectA, "projectB", projectB, "az", az)
if reason := e2eSendChangeCommitments(ctx, baseURL, transferReq, requestTimeout); reason != "" {
if !strings.Contains(reason, "not sufficient capacity") && !strings.Contains(reason, "insufficient CPU cores") {
panic(fmt.Sprintf("transfer check: transfer rejected with unexpected reason for %s: %s", resourceName, reason))
}
// Transfer rejected — commitment is still in projectA (API rolled back to original project).
- slog.Info("transfer check: transfer rejected — no capacity for project-B slots, will clean up from projectA",
+ e2eLog.Info("transfer check: transfer rejected — no capacity for project-B slots, will clean up from projectA",
"resource", resourceName, "reason", reason)
return
}
// Transfer accepted — update cleanup target to projectB.
cleanupProject = projectB
- slog.Info("transfer check: transfer accepted", "resource", resourceName, "uuid", testUUID, "projectB", projectB)
+ e2eLog.V(1).Info("transfer check: transfer accepted", "resource", resourceName, "uuid", testUUID, "projectB", projectB)
report := e2eFetchUsageReport(ctx, baseURL, az, projectB)
e2eLogUsageReport(report, az, projectB)
@@ -842,11 +844,11 @@ func e2eMarketplaceTransferResource(
// RunCommitmentsE2EChecks runs all e2e checks for the commitments API.
func RunCommitmentsE2EChecks(ctx context.Context, config E2EChecksConfig) {
- slog.Info("running commitments e2e checks")
+ e2eLog.Info("running commitments e2e checks")
CheckCommitmentsInfoEndpoint(ctx, config)
CheckCommitmentsDryRun(ctx, config)
CheckCommitmentsRoundTrip(ctx, config)
CheckCommitmentsMultiFlavorGroupBatch(ctx, config)
CheckCommitmentsMarketplaceTransfer(ctx, config)
- slog.Info("all commitments e2e checks passed")
+ e2eLog.Info("all commitments e2e checks passed")
}
diff --git a/internal/scheduling/reservations/commitments/reservation_controller.go b/internal/scheduling/reservations/commitments/reservation_controller.go
index ba10dfcd5..249d10e47 100644
--- a/internal/scheduling/reservations/commitments/reservation_controller.go
+++ b/internal/scheduling/reservations/commitments/reservation_controller.go
@@ -190,7 +190,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr
len(res.Spec.CommittedResourceReservation.Allocations) > 0 &&
res.Spec.TargetHost != "" {
// mark as ready without calling the placement API
- logger.Info("detected pre-allocated reservation",
+ logger.V(1).Info("detected pre-allocated reservation",
"targetHost", res.Spec.TargetHost,
"allocatedVMs", len(res.Spec.CommittedResourceReservation.Allocations))
@@ -243,7 +243,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr
// Object was deleted, no need to continue
return ctrl.Result{}, nil
}
- logger.Info("synced spec to status and marked ready", "host", res.Status.Host)
+ logger.V(1).Info("synced spec to status and marked ready", "host", res.Status.Host)
// Return and let next reconcile handle allocation verification
return ctrl.Result{}, nil
}
@@ -283,7 +283,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr
knowledge := &reservations.FlavorGroupKnowledgeClient{Client: r.Client}
flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil)
if err != nil {
- logger.Info("flavor knowledge not ready, requeueing",
+ logger.V(1).Info("flavor knowledge not ready, requeueing",
"resourceName", resourceName,
"error", err)
return ctrl.Result{RequeueAfter: r.Conf.RequeueIntervalRetry.Duration}, nil
@@ -331,7 +331,7 @@ func (r *CommitmentReservationController) Reconcile(ctx context.Context, req ctr
// Select appropriate pipeline based on flavor group
pipelineName := r.getPipelineForFlavorGroup(flavorGroupName, logger)
- logger.Info("selected pipeline for CR reservation",
+ logger.V(1).Info("selected pipeline for CR reservation",
"flavorName", resourceName,
"flavorGroup", flavorGroupName,
"pipeline", pipelineName,
@@ -464,7 +464,7 @@ func (r *CommitmentReservationController) reconcileAllocations(ctx context.Conte
return nil, fmt.Errorf("failed to get hypervisor %s: %w", expectedHost, err)
}
// Hypervisor not found — treat all post-grace-period VMs as stale.
- logger.Info("hypervisor CRD not found", "host", expectedHost)
+ logger.V(1).Info("hypervisor CRD not found", "host", expectedHost)
} else {
// Build set of all VM UUIDs on this hypervisor for O(1) lookup.
// Include both active and inactive VMs — stopped/shelved VMs still hold the slot.
@@ -522,20 +522,10 @@ func (r *CommitmentReservationController) reconcileAllocations(ctx context.Conte
allocationAge := now.Sub(allocation.CreationTimestamp.Time)
isInGracePeriod := allocationAge < r.Conf.AllocationGracePeriod.Duration
- // Confirmed VMs (already in Status.Allocations) bypass the grace period:
- // their departure from the HV CRD is authoritative and must be acted on immediately.
- // Unconfirmed VMs still within the grace period may not yet appear in the HV CRD
- // (still spawning), so defer verification and requeue with a short interval.
_, isConfirmed := existingStatusAllocations[vmUUID]
- if !isConfirmed && isInGracePeriod {
- result.HasAllocationsInGracePeriod = true
- logger.V(1).Info("allocation in grace period, deferring verification",
- "vm", vmUUID,
- "allocationAge", allocationAge)
- continue
- }
- // Post-grace-period or confirmed VM: use HV CRD as authoritative source.
+ // Always check HV CRD first: if the VM is already present (e.g. a long-running
+ // PayG VM being absorbed), confirm immediately without waiting for the grace period.
if hvInstanceSet[vmUUID] {
newStatusAllocations[vmUUID] = expectedHost
logger.V(1).Info("verified VM allocation via Hypervisor CRD",
@@ -544,8 +534,18 @@ func (r *CommitmentReservationController) reconcileAllocations(ctx context.Conte
continue
}
- // VM not on the expected host. For unconfirmed post-grace VMs this is a clean
- // stale allocation — remove it without further searching.
+ // VM not yet visible in HV CRD. For unconfirmed VMs still within the grace period
+ // the HV CRD sync may not have caught up yet (newly spawned VM) — defer and requeue.
+ if !isConfirmed && isInGracePeriod {
+ result.HasAllocationsInGracePeriod = true
+ logger.V(1).Info("allocation in grace period, deferring verification",
+ "vm", vmUUID,
+ "allocationAge", allocationAge)
+ continue
+ }
+
+ // VM not on the expected host, past grace period or already confirmed.
+ // For unconfirmed post-grace VMs this is a clean stale allocation — remove it.
if !isConfirmed {
allocationsToRemove = append(allocationsToRemove, vmUUID)
logger.Info("removing stale allocation (VM not found on hypervisor)",
@@ -752,7 +752,7 @@ func (r *CommitmentReservationController) getPipelineForFlavorGroup(flavorGroupN
return pipeline
}
- logger.Info("no pipeline configured for flavor group, using default", "flavorGroup", flavorGroupName, "defaultPipeline", r.Conf.PipelineDefault)
+ logger.V(1).Info("no pipeline configured for flavor group, using default", "flavorGroup", flavorGroupName, "defaultPipeline", r.Conf.PipelineDefault)
return r.Conf.PipelineDefault
}
diff --git a/internal/scheduling/reservations/commitments/reservation_manager.go b/internal/scheduling/reservations/commitments/reservation_manager.go
index 565082e15..aa6b89384 100644
--- a/internal/scheduling/reservations/commitments/reservation_manager.go
+++ b/internal/scheduling/reservations/commitments/reservation_manager.go
@@ -18,6 +18,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// ApplyResult contains the result of applying a commitment state.
@@ -63,6 +64,9 @@ type ReservationManagerConfig struct {
// NoHostsFoundTTL is the minimum age of a NoHostsFound condition before the slot is
// deleted and its memory reclaimed into the delta. A zero value disables TTL expiry.
NoHostsFoundTTL time.Duration
+ // OwnerObject, when set, is used to attach an owner reference to each newly created
+ // Reservation so the API server cascade-deletes slots when the owner is deleted.
+ OwnerObject client.Object
}
// ReservationManager handles CRUD operations for Reservation CRDs.
@@ -271,7 +275,10 @@ func (m *ReservationManager) ApplyCommitmentState(
slotMemoryBytes = deltaMemoryBytes
}
slotMemoryMB := uint64(slotMemoryBytes) / (1024 * 1024)
- reservation := m.newPaygReservation(desiredState, nextSlotIndex, candidate, slotMemoryMB, flavorGroup, creator)
+ reservation, err := m.newPaygReservation(desiredState, nextSlotIndex, candidate, slotMemoryMB, flavorGroup, creator)
+ if err != nil {
+ return result, fmt.Errorf("failed to set owner reference on PAYG reservation slot %d: %w", nextSlotIndex, err)
+ }
deltaMemoryBytes -= slotMemoryBytes
result.Created++
result.TouchedReservations = append(result.TouchedReservations, *reservation)
@@ -282,7 +289,7 @@ func (m *ReservationManager) ApplyCommitmentState(
}
return result, fmt.Errorf("failed to create PAYG reservation slot %d: %w", nextSlotIndex, err)
}
- log.Info("created PAYG pre-allocated reservation slot",
+ log.V(1).Info("created PAYG pre-allocated reservation slot",
"slot", reservation.Name,
"host", candidate.HVName,
"vm", candidate.VMID,
@@ -302,7 +309,7 @@ func (m *ReservationManager) ApplyCommitmentState(
}
}
}
- log.Info("PAYG remapping done", "slotsCreated", result.Created, "durationMs", time.Since(scanStart).Milliseconds())
+ log.V(1).Info("PAYG remapping done", "slotsCreated", result.Created, "durationMs", time.Since(scanStart).Milliseconds())
} else {
log.V(1).Info("PAYG pre-allocation skipped",
"enabled", m.cfg.EnablePaygPreAllocation,
@@ -318,7 +325,7 @@ func (m *ReservationManager) ApplyCommitmentState(
if m.cfg.MaxSlots > 0 && newSlots > m.cfg.MaxSlots {
return nil, &SlotLimitExceededError{NewSlots: newSlots, Limit: m.cfg.MaxSlots}
}
- log.Info("creating reservation slots",
+ log.V(1).Info("creating reservation slots",
"commitmentUUID", desiredState.CommitmentUUID,
"slots", newSlots,
"slotCreationDelay", m.cfg.SlotCreationDelay,
@@ -331,7 +338,10 @@ func (m *ReservationManager) ApplyCommitmentState(
for deltaMemoryBytes >= smallestMemBytes {
// Select the largest flavor that fits the remaining delta (flavors sorted descending by memory).
flavorInGroup, memoryBytes := selectFlavor(deltaMemoryBytes, flavorGroup)
- reservation := m.newReservation(desiredState, nextSlotIndex, flavorInGroup, memoryBytes, creator)
+ reservation, err := m.newReservation(desiredState, nextSlotIndex, flavorInGroup, memoryBytes, creator)
+ if err != nil {
+ return result, fmt.Errorf("failed to set owner reference on reservation slot %d: %w", nextSlotIndex, err)
+ }
result.TouchedReservations = append(result.TouchedReservations, *reservation)
deltaMemoryBytes -= memoryBytes
result.Created++
@@ -475,7 +485,7 @@ func (m *ReservationManager) newReservation(
flavorInGroup compute.FlavorInGroup,
memoryBytes int64,
creator string,
-) *v1alpha1.Reservation {
+) (*v1alpha1.Reservation, error) {
namePrefix := state.NamePrefix
if namePrefix == "" {
@@ -521,7 +531,7 @@ func (m *ReservationManager) newReservation(
spec.EndTime = &metav1.Time{Time: *state.EndTime}
}
- return &v1alpha1.Reservation{
+ reservation := &v1alpha1.Reservation{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
@@ -533,6 +543,12 @@ func (m *ReservationManager) newReservation(
},
Spec: spec,
}
+ if m.cfg.OwnerObject != nil {
+ if err := controllerutil.SetControllerReference(m.cfg.OwnerObject, reservation, m.Scheme()); err != nil {
+ return nil, err
+ }
+ }
+ return reservation, nil
}
// newPaygReservation creates a Reservation pre-populated with a PAYG VM.
@@ -546,7 +562,7 @@ func (m *ReservationManager) newPaygReservation(
slotMemoryMB uint64,
flavorGroup compute.FlavorGroupFeature,
creator string,
-) *v1alpha1.Reservation {
+) (*v1alpha1.Reservation, error) {
namePrefix := state.NamePrefix
if namePrefix == "" {
@@ -600,7 +616,7 @@ func (m *ReservationManager) newPaygReservation(
spec.EndTime = &metav1.Time{Time: *state.EndTime}
}
- return &v1alpha1.Reservation{
+ reservation := &v1alpha1.Reservation{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
@@ -612,4 +628,10 @@ func (m *ReservationManager) newPaygReservation(
},
Spec: spec,
}
+ if m.cfg.OwnerObject != nil {
+ if err := controllerutil.SetControllerReference(m.cfg.OwnerObject, reservation, m.Scheme()); err != nil {
+ return nil, err
+ }
+ }
+ return reservation, nil
}
diff --git a/internal/scheduling/reservations/commitments/reservation_manager_test.go b/internal/scheduling/reservations/commitments/reservation_manager_test.go
index e05cf8f56..0ee1209a7 100644
--- a/internal/scheduling/reservations/commitments/reservation_manager_test.go
+++ b/internal/scheduling/reservations/commitments/reservation_manager_test.go
@@ -95,6 +95,7 @@ func TestApplyCommitmentState(t *testing.T) {
noHostsFoundTTL time.Duration // 0 = disabled
wantError bool
wantRemovedCount int // exact count; -1 = at least one
+ ownerObject client.Object
validateRemoved func(t *testing.T, removed []v1alpha1.Reservation)
validateTouched func(t *testing.T, touched []v1alpha1.Reservation)
validateRemaining func(t *testing.T, remaining []v1alpha1.Reservation)
@@ -102,6 +103,7 @@ func TestApplyCommitmentState(t *testing.T) {
{
name: "creates reservations to match desired memory",
desiredMemoryGiB: 24, // 3 × 8 GiB slots
+ ownerObject: &v1alpha1.CommittedResource{ObjectMeta: metav1.ObjectMeta{Name: "test-cr", UID: "test-uid-1234"}},
validateTouched: func(t *testing.T, touched []v1alpha1.Reservation) {
if len(touched) == 0 {
t.Fatal("expected at least one reservation created")
@@ -462,7 +464,7 @@ func TestApplyCommitmentState(t *testing.T) {
objects[i] = &tt.existingSlots[i]
}
k8sClient := newCRTestClient(scheme, objects...)
- manager := NewReservationManager(k8sClient, ReservationManagerConfig{MaxSlots: tt.maxSlots, NoHostsFoundTTL: tt.noHostsFoundTTL})
+ manager := NewReservationManager(k8sClient, ReservationManagerConfig{MaxSlots: tt.maxSlots, NoHostsFoundTTL: tt.noHostsFoundTTL, OwnerObject: tt.ownerObject})
flavorGroups := testFlavorGroups()
if tt.flavorGroupOverride != nil {
@@ -519,6 +521,25 @@ func TestApplyCommitmentState(t *testing.T) {
}
tt.validateRemaining(t, remaining.Items)
}
+ for _, r := range applyResult.TouchedReservations {
+ if tt.ownerObject != nil {
+ if len(r.OwnerReferences) != 1 {
+ t.Errorf("expected 1 owner reference, got %d", len(r.OwnerReferences))
+ continue
+ }
+ if r.OwnerReferences[0].Name != tt.ownerObject.GetName() {
+ t.Errorf("owner reference name: want %q, got %q", tt.ownerObject.GetName(), r.OwnerReferences[0].Name)
+ }
+ if r.OwnerReferences[0].UID != tt.ownerObject.GetUID() {
+ t.Errorf("owner reference UID: want %q, got %q", tt.ownerObject.GetUID(), r.OwnerReferences[0].UID)
+ }
+ if r.OwnerReferences[0].Controller == nil || !*r.OwnerReferences[0].Controller {
+ t.Error("expected Controller=true on owner reference")
+ }
+ } else if len(r.OwnerReferences) != 0 {
+ t.Errorf("expected 0 owner references, got %d", len(r.OwnerReferences))
+ }
+ }
})
}
@@ -590,6 +611,7 @@ func TestApplyCommitmentState_PAYG(t *testing.T) {
existingSlots []v1alpha1.Reservation
desiredMemoryGiB int64
enablePayg bool // must be set true to activate PAYG; false = flag disabled (Phase 5 only)
+ ownerObject client.Object
validateTouched func(t *testing.T, touched []v1alpha1.Reservation)
}{
{
@@ -598,6 +620,7 @@ func TestApplyCommitmentState_PAYG(t *testing.T) {
paygVMs: []reservations.VM{paygVM(vmUUID, "small")},
enablePayg: true,
desiredMemoryGiB: 8,
+ ownerObject: &v1alpha1.CommittedResource{ObjectMeta: metav1.ObjectMeta{Name: "test-cr", UID: "test-uid-1234"}},
validateTouched: func(t *testing.T, touched []v1alpha1.Reservation) {
if len(touched) != 1 {
t.Fatalf("want 1 slot, got %d", len(touched))
@@ -831,6 +854,7 @@ func TestApplyCommitmentState_PAYG(t *testing.T) {
mgr := NewReservationManager(k8sClient, ReservationManagerConfig{
EnablePaygPreAllocation: tt.enablePayg,
VMSource: &fakeVMSource{vms: tt.paygVMs},
+ OwnerObject: tt.ownerObject,
})
desiredState := &CommitmentState{
@@ -850,6 +874,25 @@ func TestApplyCommitmentState_PAYG(t *testing.T) {
if tt.validateTouched != nil {
tt.validateTouched(t, result.TouchedReservations)
}
+ for _, r := range result.TouchedReservations {
+ if tt.ownerObject != nil {
+ if len(r.OwnerReferences) != 1 {
+ t.Errorf("expected 1 owner reference, got %d", len(r.OwnerReferences))
+ continue
+ }
+ if r.OwnerReferences[0].Name != tt.ownerObject.GetName() {
+ t.Errorf("owner reference name: want %q, got %q", tt.ownerObject.GetName(), r.OwnerReferences[0].Name)
+ }
+ if r.OwnerReferences[0].UID != tt.ownerObject.GetUID() {
+ t.Errorf("owner reference UID: want %q, got %q", tt.ownerObject.GetUID(), r.OwnerReferences[0].UID)
+ }
+ if r.OwnerReferences[0].Controller == nil || !*r.OwnerReferences[0].Controller {
+ t.Error("expected Controller=true on owner reference")
+ }
+ } else if len(r.OwnerReferences) != 0 {
+ t.Errorf("expected 0 owner references, got %d", len(r.OwnerReferences))
+ }
+ }
})
}
@@ -1091,7 +1134,10 @@ func TestNewReservation_SelectsAppropriateFlavor(t *testing.T) {
}
flavorInGroup, memoryBytes := selectFlavor(tt.deltaMemory, flavorGroup)
- reservation := manager.newReservation(state, 0, flavorInGroup, memoryBytes, "syncer")
+ reservation, err := manager.newReservation(state, 0, flavorInGroup, memoryBytes, "syncer")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
if reservation.Spec.CommittedResourceReservation.ResourceName != tt.expectedName {
t.Errorf("expected flavor %s, got %s",
@@ -1177,7 +1223,10 @@ func TestNewReservation_VariableRatioGroup_SelectsLargestByMemory(t *testing.T)
FlavorGroupName: "gp-group",
}
flavorInGroup, memoryBytes := selectFlavor(deltaBytes, fg)
- res := manager.newReservation(state, 0, flavorInGroup, memoryBytes, "test")
+ res, err := manager.newReservation(state, 0, flavorInGroup, memoryBytes, "test")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
if got := res.Spec.CommittedResourceReservation.ResourceName; got != tt.wantFlavor {
t.Errorf("flavor: want %s, got %s", tt.wantFlavor, got)
}
diff --git a/internal/scheduling/reservations/commitments/syncer.go b/internal/scheduling/reservations/commitments/syncer.go
index 163032ee1..ef5bcf97a 100644
--- a/internal/scheduling/reservations/commitments/syncer.go
+++ b/internal/scheduling/reservations/commitments/syncer.go
@@ -122,7 +122,7 @@ func (s *Syncer) getCommitmentStates(ctx context.Context, log logr.Logger, flavo
}
for id, commitment := range commitments {
if commitment.ServiceType != "compute" {
- log.Info("skipping non-compute commitment", "id", id, "serviceType", commitment.ServiceType)
+ log.V(1).Info("skipping non-compute commitment", "id", id, "serviceType", commitment.ServiceType)
if s.monitor != nil {
s.monitor.RecordCommitmentSkipped(SkipReasonNonCompute)
}
@@ -149,7 +149,7 @@ func (s *Syncer) getCommitmentStates(ctx context.Context, log logr.Logger, flavo
// Extract flavor group name from resource name (validates format: hw_version__ram)
flavorGroupName, err := GetFlavorGroupNameFromResource(commitment.ResourceName)
if err != nil {
- log.Info("skipping commitment with invalid resource name",
+ log.V(1).Info("skipping commitment with invalid resource name",
"id", id,
"resourceName", commitment.ResourceName,
"error", err)
@@ -200,7 +200,7 @@ func (s *Syncer) getCommitmentStates(ctx context.Context, log logr.Logger, flavo
// Skip commitments with empty UUID
if commitment.UUID == "" {
- log.Info("skipping commitment with empty UUID",
+ log.V(1).Info("skipping commitment with empty UUID",
"id", id)
if s.monitor != nil {
s.monitor.RecordCommitmentSkipped(SkipReasonEmptyUUID)
@@ -217,7 +217,7 @@ func (s *Syncer) getCommitmentStates(ctx context.Context, log logr.Logger, flavo
continue
}
- log.Info("resolved commitment to state",
+ log.V(1).Info("resolved commitment to state",
"commitmentID", commitment.UUID,
"flavorGroup", flavorGroupName,
"amount", commitment.Amount,
@@ -301,7 +301,7 @@ func (s *Syncer) SyncReservations(ctx context.Context) error {
// Upsert CommittedResource CRDs for each commitment
var totalCreated, totalUpdated int
for _, state := range commitmentResult.states {
- logger.Info("upserting committed resource CRD",
+ logger.V(1).Info("upserting committed resource CRD",
"commitmentUUID", state.CommitmentUUID,
"projectID", state.ProjectID,
"flavorGroup", state.FlavorGroupName,
@@ -389,7 +389,7 @@ func (s *Syncer) SyncReservations(ctx context.Context) error {
staleDeleted++
}
} else {
- logger.Info("stale committed resource CRD: present locally but absent from Limes",
+ logger.V(1).Info("stale committed resource CRD: present locally but absent from Limes",
"name", cr.Name, "commitmentUUID", cr.Spec.CommitmentUUID)
staleCRCount++
}
@@ -426,12 +426,12 @@ func (s *Syncer) SyncReservations(ctx context.Context) error {
for i := range existingReservations.Items {
res := &existingReservations.Items[i]
if res.Spec.CommittedResourceReservation == nil {
- logger.Info("skipping reservation without committed resource spec", "name", res.Name)
+ logger.V(1).Info("skipping reservation without committed resource spec", "name", res.Name)
continue
}
commitmentUUID := res.Spec.CommittedResourceReservation.CommitmentUUID
if commitmentUUID == "" {
- logger.Info("skipping reservation with empty commitment UUID", "name", res.Name)
+ logger.V(1).Info("skipping reservation with empty commitment UUID", "name", res.Name)
continue
}
if !activeCommitments[commitmentUUID] {
diff --git a/internal/scheduling/reservations/commitments/usage.go b/internal/scheduling/reservations/commitments/usage.go
index dbf1c08f7..90ab813d9 100644
--- a/internal/scheduling/reservations/commitments/usage.go
+++ b/internal/scheduling/reservations/commitments/usage.go
@@ -136,7 +136,7 @@ func (c *UsageCalculator) CalculateUsage(
assignedToCommitments++
}
}
- log.Info("completed usage report",
+ log.V(1).Info("completed usage report",
"projectID", projectID,
"vmCount", len(vms),
"assignedToCommitments", assignedToCommitments,
diff --git a/internal/scheduling/reservations/commitments/usage_reconciler.go b/internal/scheduling/reservations/commitments/usage_reconciler.go
index b15aa0777..0e350036a 100644
--- a/internal/scheduling/reservations/commitments/usage_reconciler.go
+++ b/internal/scheduling/reservations/commitments/usage_reconciler.go
@@ -50,7 +50,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
// Only active commitments have assigned VMs. Clear stale usage status if present.
if !cr.IsActive() {
- log.Info("skipping: commitment state is not active", "state", cr.Spec.State)
+ log.V(1).Info("skipping: commitment state is not active", "state", cr.Spec.State)
if len(cr.Status.AssignedInstances) > 0 || len(cr.Status.UsedResources) > 0 {
old := cr.DeepCopy()
cr.Status.AssignedInstances = nil
@@ -69,7 +69,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
// The state field may not reflect expiry immediately (syncer updates on its own schedule),
// so we check EndTime directly to prevent stale assignments from persisting past expiry.
if cr.Spec.EndTime != nil && cr.Spec.EndTime.Time.Before(start) {
- log.Info("skipping: commitment has expired", "endTime", cr.Spec.EndTime)
+ log.V(1).Info("skipping: commitment has expired", "endTime", cr.Spec.EndTime)
if len(cr.Status.AssignedInstances) > 0 || len(cr.Status.UsedResources) > 0 {
old := cr.DeepCopy()
cr.Status.AssignedInstances = nil
@@ -84,7 +84,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, nil
}
if cr.Spec.StartTime != nil && cr.Spec.StartTime.After(start) {
- log.Info("skipping: commitment has not started yet", "startTime", cr.Spec.StartTime)
+ log.V(1).Info("skipping: commitment has not started yet", "startTime", cr.Spec.StartTime)
return ctrl.Result{}, nil
}
@@ -97,7 +97,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
// condition is written, triggering a fresh reconcile at that point.
readyCond := meta.FindStatusCondition(cr.Status.Conditions, v1alpha1.CommittedResourceConditionReady)
if readyCond == nil || readyCond.ObservedGeneration != cr.Generation || readyCond.Status != metav1.ConditionTrue {
- log.Info("skipping: Ready condition not yet accepted for current generation",
+ log.V(1).Info("skipping: Ready condition not yet accepted for current generation",
"generation", cr.Generation,
"readyCondFound", readyCond != nil,
)
@@ -121,7 +121,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
trigger = "generation-change"
}
logger := log
- logger.Info("usage reconcile starting", "trigger", trigger, "generation", cr.Generation)
+ logger.V(1).Info("usage reconcile starting", "trigger", trigger, "generation", cr.Generation)
knowledge := &reservations.FlavorGroupKnowledgeClient{Client: r.Client}
flavorGroups, err := knowledge.GetAllFlavorGroups(ctx, nil)
@@ -136,7 +136,7 @@ func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, err
}
if len(commitmentsByAZFG) == 0 {
- logger.Info("no active commitments found for project, retrying after cooldown")
+ logger.V(1).Info("no active commitments found for project, retrying after cooldown")
return ctrl.Result{RequeueAfter: cooldown}, nil
}
@@ -350,7 +350,7 @@ func (p acceptedGenerationPredicate) Update(e event.UpdateEvent) bool {
if newCR.Status.UsageObservedGeneration != nil && *newCR.Status.UsageObservedGeneration >= newCond.ObservedGeneration {
return false
}
- p.log.Info("predicate fired: Ready accepted, usage not yet up to date",
+ p.log.V(1).Info("predicate fired: Ready accepted, usage not yet up to date",
"name", newCR.Name, "generation", newCR.Generation,
"usageObservedGeneration", newCR.Status.UsageObservedGeneration)
return true
diff --git a/internal/scheduling/reservations/failover/controller.go b/internal/scheduling/reservations/failover/controller.go
index c9bd443ba..213708245 100644
--- a/internal/scheduling/reservations/failover/controller.go
+++ b/internal/scheduling/reservations/failover/controller.go
@@ -73,7 +73,7 @@ type vmFailoverNeed struct {
func (c *FailoverReservationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
ctx = WithNewGlobalRequestID(ctx)
logger := LoggerFromContext(ctx).WithValues("reservation", req.Name, "namespace", req.Namespace)
- logger.Info("reconciling failover reservation", "reservation", req.Name)
+ logger.V(1).Info("reconciling failover reservation", "reservation", req.Name)
// Fetch the reservation
var res v1alpha1.Reservation
@@ -441,19 +441,19 @@ func reconcileRemoveInvalidVMFromReservations(
// prevents a postgres data loss from cascading into a mass
// deletion of failover reservations.
if hv, stillOnHV := vmsOnHypervisor[vmUUID]; stillOnHV {
- logger.Info("keeping VM allocation despite missing from VM source: still active on hypervisor",
+ logger.V(1).Info("keeping VM allocation despite missing from VM source: still active on hypervisor",
"vmUUID", vmUUID, "reservation", res.Name,
"allocatedHypervisor", allocatedHypervisor, "hypervisor", hv)
updatedAllocations[vmUUID] = allocatedHypervisor
continue
}
- logger.Info("removing VM from reservation allocations because VM no longer exists",
+ logger.V(1).Info("removing VM from reservation allocations because VM no longer exists",
"vmUUID", vmUUID, "reservation", res.Name)
needsUpdate = true
continue
}
if vmCurrentHypervisor != allocatedHypervisor {
- logger.Info("removing VM from reservation allocations because hypervisor has changed",
+ logger.V(1).Info("removing VM from reservation allocations because hypervisor has changed",
"vmUUID", vmUUID, "reservation", res.Name,
"allocatedHypervisor", allocatedHypervisor, "currentHypervisor", vmCurrentHypervisor)
needsUpdate = true
@@ -520,7 +520,7 @@ func reconcileRemoveNoneligibleVMFromReservations(
}
}
- logger.Info("removing non-eligible VMs from reservation",
+ logger.V(1).Info("removing non-eligible VMs from reservation",
"reservation", res.Name,
"host", res.Status.Host,
"before", len(allocations),
@@ -557,7 +557,7 @@ func reconcileRemoveEmptyReservations(
if len(allocations) == 0 {
resCopy := res.DeepCopy()
reservationsToDelete = append(reservationsToDelete, resCopy)
- logger.Info("marking empty failover reservation for deletion", "reservationName", res.Name, "hypervisor", res.Status.Host)
+ logger.V(1).Info("marking empty failover reservation for deletion", "reservationName", res.Name, "hypervisor", res.Status.Host)
} else {
updatedReservations = append(updatedReservations, res)
}
@@ -661,7 +661,7 @@ func (c *FailoverReservationController) reconcileCreateAndAssignReservations(
reqID := uuid.New().String()
vmCtx := reservations.WithRequestID(ctx, reqID)
vmLogger := LoggerFromContext(vmCtx).WithValues("vmUUID", need.VM.UUID)
- vmLogger.Info("processing VM for failover reservation")
+ vmLogger.V(1).Info("processing VM for failover reservation")
// Resolve VM resources once per VM (may use LargestFlavor from flavor group)
resSpec := resolveVMSpecForScheduling(vmCtx, need.VM, c.Config.UseFlavorGroupResources, flavorGroups)
@@ -710,7 +710,7 @@ func (c *FailoverReservationController) reconcileCreateAndAssignReservations(
if err := c.patchReservationStatus(vmCtx, newRes); err != nil {
vmLogger.V(1).Info("failed to update failover reservation status", "error", err, "reservationName", newRes.Name, "status", newRes.Status)
} else {
- vmLogger.Info("successfully updated failover reservation status",
+ vmLogger.V(1).Info("successfully updated failover reservation status",
"reservationName", newRes.Name,
"host", newRes.Status.Host,
"allocations", newRes.Status.FailoverReservation.Allocations)
@@ -721,7 +721,7 @@ func (c *FailoverReservationController) reconcileCreateAndAssignReservations(
excludeHypervisors[newRes.Status.Host] = true
}
- vmLogger.Info("processed VM failover reservations",
+ vmLogger.V(1).Info("processed VM failover reservations",
"flavorName", need.VM.FlavorName,
"needed", need.Count,
"reused", vmReused,
diff --git a/internal/scheduling/reservations/failover/reservation_scheduling.go b/internal/scheduling/reservations/failover/reservation_scheduling.go
index bc9d44a3b..2cae7105a 100644
--- a/internal/scheduling/reservations/failover/reservation_scheduling.go
+++ b/internal/scheduling/reservations/failover/reservation_scheduling.go
@@ -86,7 +86,7 @@ func (c *FailoverReservationController) queryHypervisorsFromScheduler(ctx contex
}
if len(eligibleHypervisors) == 0 {
- logger.Info("no eligible hypervisors for failover reservation",
+ logger.V(1).Info("no eligible hypervisors for failover reservation",
"vmCurrentHypervisor", vm.CurrentHypervisor)
return nil, fmt.Errorf("no eligible hypervisors for failover reservation (VM is on %s)", vm.CurrentHypervisor)
}
@@ -171,13 +171,13 @@ func (c *FailoverReservationController) tryReuseExistingReservation(
return nil
}
if len(validHypervisors) == 0 {
- logger.Info("no potential hypervisors returned by scheduler for VM", "vmUUID", vm.UUID)
+ logger.V(1).Info("no potential hypervisors returned by scheduler for VM", "vmUUID", vm.UUID)
return nil
}
eligibleReservations := FindEligibleReservations(vm, failoverReservations)
if len(eligibleReservations) == 0 {
- logger.Info("no eligible reservations found for VM", "vmUUID", vm.UUID)
+ logger.V(1).Info("no eligible reservations found for VM", "vmUUID", vm.UUID)
return nil
}
@@ -344,7 +344,7 @@ func (c *FailoverReservationController) scheduleAndBuildNewFailoverReservation(
}
if selectedHypervisor == "" {
- logger.Info("no eligible hypervisors after constraint checking", "vmUUID", vm.UUID, "schedulerReturnedCount", len(validHypervisors))
+ logger.V(1).Info("no eligible hypervisors after constraint checking", "vmUUID", vm.UUID, "schedulerReturnedCount", len(validHypervisors))
return nil, fmt.Errorf("no eligible hypervisors after constraint checking (scheduler returned %d hypervisors, all rejected)", len(validHypervisors))
}
diff --git a/internal/scheduling/reservations/inflight/controller.go b/internal/scheduling/reservations/inflight/controller.go
index 230840f60..8ce4f22fe 100644
--- a/internal/scheduling/reservations/inflight/controller.go
+++ b/internal/scheduling/reservations/inflight/controller.go
@@ -70,7 +70,7 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
if apierrors.IsNotFound(err) {
// If the custom resource is not found then it usually means
// that it was deleted or not created.
- log.Info("Resource not found. Ignoring since object must be deleted")
+ log.V(1).Info("Resource not found. Ignoring since object must be deleted")
return ctrl.Result{}, nil
}
// Error reading the object - requeue the request.
diff --git a/internal/scheduling/reservations/inflight/vm_client.go b/internal/scheduling/reservations/inflight/vm_client.go
index bcdcdebe7..2c3854f0d 100644
--- a/internal/scheduling/reservations/inflight/vm_client.go
+++ b/internal/scheduling/reservations/inflight/vm_client.go
@@ -58,7 +58,7 @@ func (c *novaVMClient) StartWithKubernetesSecrets(ctx context.Context, client cl
log.Error(err, "failed to create SSO authenticated HTTP client")
return err
}
- log.Info("successfully created SSO authenticated HTTP client")
+ log.V(1).Info("successfully created SSO authenticated HTTP client")
}
authenticatedKeystone, err := keystone.
Connector{Client: client, HTTPClient: authenticatedHTTP}.
@@ -67,7 +67,7 @@ func (c *novaVMClient) StartWithKubernetesSecrets(ctx context.Context, client cl
log.Error(err, "failed to create authenticated keystone client")
return err
}
- log.Info("successfully created authenticated keystone client")
+ log.V(1).Info("successfully created authenticated keystone client")
// Automatically fetch the nova endpoint from the keystone service catalog.
provider := authenticatedKeystone.Client()
serviceType := "compute"
@@ -115,6 +115,6 @@ func (c *novaVMClient) GetCurrentVMSize(ctx context.Context, vmID string) (map[h
hv1.ResourceMemory: *resource.
NewQuantity(server.Flavor.RAM*1024*1024, resource.BinarySI),
}
- log.Info("successfully retrieved VM size from nova", "vmID", vmID, "size", size)
+ log.V(1).Info("successfully retrieved VM size from nova", "vmID", vmID, "size", size)
return size, nil
}
diff --git a/internal/scheduling/reservations/quota/controller.go b/internal/scheduling/reservations/quota/controller.go
index 122898b0b..a64ae07a5 100644
--- a/internal/scheduling/reservations/quota/controller.go
+++ b/internal/scheduling/reservations/quota/controller.go
@@ -84,7 +84,7 @@ func (c *QuotaController) ReconcilePeriodic(ctx context.Context) error {
// Build flavorName → flavorGroup lookup
flavorToGroup := buildFlavorToGroupMap(flavorGroups)
- logger.Info("flavor group knowledge loaded",
+ logger.V(1).Info("flavor group knowledge loaded",
"flavorGroups", len(flavorGroups),
"totalFlavorsInMap", len(flavorToGroup))
@@ -106,7 +106,7 @@ func (c *QuotaController) ReconcilePeriodic(ctx context.Context) error {
mappedVMs++
}
}
- logger.Info("VM flavor mapping complete",
+ logger.V(1).Info("VM flavor mapping complete",
"totalVMs", len(vms), "mappedVMs", mappedVMs, "unmappedVMs", len(vms)-mappedVMs,
"projectsWithUsage", len(totalUsageByProject))
@@ -201,7 +201,7 @@ func (c *QuotaController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
var totalUsage map[string]map[string]int64
if specChanged {
// Spec changed (new CRD or quota update) — recompute TotalUsage from Postgres
- logger.Info("spec changed, recomputing TotalUsage from Postgres",
+ logger.V(1).Info("spec changed, recomputing TotalUsage from Postgres",
"generation", pq.Generation, "observedGeneration", pq.Status.ObservedGeneration)
var err error
totalUsage, err = c.computeTotalUsageForProject(ctx, projectID)
@@ -217,7 +217,7 @@ func (c *QuotaController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
totalUsage = expandAZSlice(pq.Status.TotalUsage, pq.Spec.AvailabilityZone)
} else {
// Safety fallback: TotalUsage should always be set after first spec reconcile
- logger.Info("no TotalUsage persisted, computing as fallback")
+ logger.V(1).Info("no TotalUsage persisted, computing as fallback")
var err error
totalUsage, err = c.computeTotalUsageForProject(ctx, projectID)
if err != nil {
diff --git a/pkg/conf/opts.go b/pkg/conf/opts.go
index a0d945a9f..ece31fbec 100644
--- a/pkg/conf/opts.go
+++ b/pkg/conf/opts.go
@@ -5,9 +5,12 @@ package conf
import (
"encoding/json"
- "log/slog"
+
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("conf")
+
// Raw options that are not directly unmarshalled when loading from json.
// Usage: call Unmarshal to unmarshal the options into a struct.
type RawOpts struct {
@@ -59,7 +62,7 @@ func (s *JsonOpts[Options]) Load(opts RawOpts) error {
if err := opts.Unmarshal(&o); err != nil {
return err
}
- slog.Info("jsonopts: loaded", "options", o)
+ log.Info("jsonopts: loaded", "options", o)
s.Options = o
return nil
}
diff --git a/pkg/keystone/client.go b/pkg/keystone/client.go
index 2e0fa2883..43131273c 100644
--- a/pkg/keystone/client.go
+++ b/pkg/keystone/client.go
@@ -6,15 +6,17 @@ package keystone
import (
"context"
"errors"
- "log/slog"
"net/http"
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("keystone")
+
// Kubernetes connector which initializes the openstack connection from a secret.
type Connector struct {
// Kubernetes API client to use.
@@ -125,7 +127,7 @@ func (api *keystoneClient) Authenticate(ctx context.Context) error {
// Already authenticated.
return nil
}
- slog.Info("authenticating against openstack", "url", api.keystoneConf.URL)
+ log.Info("authenticating against openstack", "url", api.keystoneConf.URL)
authOptions := gophercloud.AuthOptions{
IdentityEndpoint: api.keystoneConf.URL,
Username: api.keystoneConf.OSUsername,
@@ -148,7 +150,7 @@ func (api *keystoneClient) Authenticate(ctx context.Context) error {
return err
}
api.client = provider
- slog.Info("authenticated against openstack")
+ log.Info("authenticated against openstack")
return nil
}
diff --git a/pkg/monitoring/log_metrics.go b/pkg/monitoring/log_metrics.go
index a2477bd96..0137343e6 100644
--- a/pkg/monitoring/log_metrics.go
+++ b/pkg/monitoring/log_metrics.go
@@ -4,21 +4,12 @@
package monitoring
import (
- "context"
- "log/slog"
"path"
- "runtime"
- "sync"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap/zapcore"
)
-// pcFileCache caches the resolved file path for each program counter. The set
-// of distinct PCs is bounded by the number of log call sites in the binary, so
-// this map grows to a fixed size and all subsequent lookups are lock-free reads.
-var pcFileCache sync.Map // uintptr -> string
-
// LogMetricsMonitor owns the log-message counter and implements the
// prometheus.Collector interface so it can be registered with the metrics
// registry like every other Monitor in cortex.
@@ -61,69 +52,6 @@ func shortFilePath(file string) string {
return parent + "/" + base
}
-// --- slog handler wrapper ---
-
-// MetricsSlogHandler wraps an slog.Handler and increments the log-message
-// counter for every warn or error log record.
-type MetricsSlogHandler struct {
- monitor *LogMetricsMonitor
- next slog.Handler
-}
-
-// NewMetricsSlogHandler returns a new handler that counts warn/error logs and
-// delegates all calls to next.
-func NewMetricsSlogHandler(monitor *LogMetricsMonitor, next slog.Handler) *MetricsSlogHandler {
- return &MetricsSlogHandler{monitor: monitor, next: next}
-}
-
-func (h *MetricsSlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
- if h.next == nil {
- return false
- }
- return h.next.Enabled(ctx, level)
-}
-
-func (h *MetricsSlogHandler) Handle(ctx context.Context, r slog.Record) error {
- if r.Level >= slog.LevelWarn {
- level := "warn"
- if r.Level >= slog.LevelError {
- level = "error"
- }
- file := "unknown"
- if r.PC != 0 {
- if cached, ok := pcFileCache.Load(r.PC); ok {
- file = cached.(string)
- } else {
- frames := runtime.CallersFrames([]uintptr{r.PC})
- f, _ := frames.Next()
- if f.File != "" {
- file = shortFilePath(f.File)
- }
- pcFileCache.Store(r.PC, file)
- }
- }
- h.monitor.inc(level, file)
- }
- if h.next == nil {
- return nil
- }
- return h.next.Handle(ctx, r)
-}
-
-func (h *MetricsSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
- if h.next == nil {
- return &MetricsSlogHandler{monitor: h.monitor}
- }
- return &MetricsSlogHandler{monitor: h.monitor, next: h.next.WithAttrs(attrs)}
-}
-
-func (h *MetricsSlogHandler) WithGroup(name string) slog.Handler {
- if h.next == nil {
- return &MetricsSlogHandler{monitor: h.monitor}
- }
- return &MetricsSlogHandler{monitor: h.monitor, next: h.next.WithGroup(name)}
-}
-
// --- zap core wrapper ---
// WrapCoreWithLogMetrics returns a zapcore.Core that hooks into every write to
diff --git a/pkg/monitoring/log_metrics_test.go b/pkg/monitoring/log_metrics_test.go
index a8e955ce1..d9c7541f7 100644
--- a/pkg/monitoring/log_metrics_test.go
+++ b/pkg/monitoring/log_metrics_test.go
@@ -5,8 +5,6 @@ package monitoring
import (
"bytes"
- "log/slog"
- "strings"
"testing"
"time"
@@ -86,141 +84,6 @@ func sumLevel(counts map[string]map[string]float64, level string) float64 {
return total
}
-func TestMetricsSlogHandler_Counts(t *testing.T) {
- tests := []struct {
- name string
- emit func(logger *slog.Logger)
- wantWarn float64
- wantError float64
- wantNoDebug bool
- wantNoInfo bool
- }{
- {
- name: "one warn one error",
- emit: func(l *slog.Logger) {
- l.Warn("w")
- l.Error("e")
- },
- wantWarn: 1,
- wantError: 1,
- wantNoDebug: true,
- wantNoInfo: true,
- },
- {
- name: "multiple warns",
- emit: func(l *slog.Logger) {
- l.Warn("w1")
- l.Warn("w2")
- l.Warn("w3")
- },
- wantWarn: 3,
- wantError: 0,
- wantNoDebug: true,
- wantNoInfo: true,
- },
- {
- name: "multiple errors",
- emit: func(l *slog.Logger) {
- l.Error("e1")
- l.Error("e2")
- },
- wantWarn: 0,
- wantError: 2,
- wantNoDebug: true,
- wantNoInfo: true,
- },
- {
- name: "debug and info are not counted",
- emit: func(l *slog.Logger) {
- l.Debug("d")
- l.Info("i")
- },
- wantWarn: 0,
- wantError: 0,
- wantNoDebug: true,
- wantNoInfo: true,
- },
- {
- name: "mixed levels",
- emit: func(l *slog.Logger) {
- l.Debug("d")
- l.Info("i")
- l.Warn("w1")
- l.Warn("w2")
- l.Error("e1")
- l.Error("e2")
- l.Error("e3")
- },
- wantWarn: 2,
- wantError: 3,
- wantNoDebug: true,
- wantNoInfo: true,
- },
- {
- name: "no logs emitted",
- emit: func(l *slog.Logger) {},
- wantWarn: 0,
- wantError: 0,
- wantNoDebug: true,
- wantNoInfo: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- monitor := NewLogMetricsMonitor()
- var buf bytes.Buffer
- inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
- logger := slog.New(NewMetricsSlogHandler(&monitor, inner))
- tt.emit(logger)
- counts := gatherCounts(t, &monitor)
- if got := sumLevel(counts, "warn"); got != tt.wantWarn {
- t.Errorf("warn count: got %v, want %v", got, tt.wantWarn)
- }
- if got := sumLevel(counts, "error"); got != tt.wantError {
- t.Errorf("error count: got %v, want %v", got, tt.wantError)
- }
- if tt.wantNoDebug && len(counts["debug"]) > 0 {
- t.Errorf("expected no debug counts, got %v", counts["debug"])
- }
- if tt.wantNoInfo && len(counts["info"]) > 0 {
- t.Errorf("expected no info counts, got %v", counts["info"])
- }
- })
- }
-}
-
-func TestMetricsSlogHandler_DelegatesAllLevels(t *testing.T) {
- monitor := NewLogMetricsMonitor()
- var buf bytes.Buffer
- inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
- logger := slog.New(NewMetricsSlogHandler(&monitor, inner))
- logger.Debug("d")
- logger.Info("i")
- logger.Warn("w")
- logger.Error("e")
- output := buf.String()
- for _, msg := range []string{"msg=d", "msg=i", "msg=w", "msg=e"} {
- if !strings.Contains(output, msg) {
- t.Errorf("expected inner handler to receive %q", msg)
- }
- }
-}
-
-func TestMetricsSlogHandler_WithAttrsAndGroup(t *testing.T) {
- monitor := NewLogMetricsMonitor()
- var buf bytes.Buffer
- inner := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})
- handler := NewMetricsSlogHandler(&monitor, inner)
- derived := handler.WithAttrs([]slog.Attr{slog.String("key", "val")})
- grouped := derived.WithGroup("grp")
- if _, ok := derived.(*MetricsSlogHandler); !ok {
- t.Fatalf("WithAttrs should return *MetricsSlogHandler, got %T", derived)
- }
- if _, ok := grouped.(*MetricsSlogHandler); !ok {
- t.Fatalf("WithGroup should return *MetricsSlogHandler, got %T", grouped)
- }
-}
-
func TestLogMetricsMonitor_DescribeCollect(t *testing.T) {
monitor := NewLogMetricsMonitor()
// Verify Describe sends at least one descriptor.
diff --git a/pkg/sso/sso.go b/pkg/sso/sso.go
index ac86e089c..90d2f091d 100644
--- a/pkg/sso/sso.go
+++ b/pkg/sso/sso.go
@@ -9,13 +9,15 @@ import (
"crypto/x509"
"errors"
"fmt"
- "log/slog"
"net/http"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
)
+var log = logf.Log.WithName("sso")
+
// userAgent is the User-Agent value set on every outgoing HTTP request made
// through clients created by this package, identifying cortex as the caller.
// It defaults to "cortex" and can be overridden once at process startup via
@@ -53,7 +55,7 @@ type requestLogger struct {
// RoundTrip logs the request URL before making the request.
func (lrt *requestLogger) RoundTrip(req *http.Request) (*http.Response, error) {
- slog.Info("making http request", "url", req.URL.String())
+ log.V(1).Info("making http request", "url", req.URL.String())
return lrt.T.RoundTrip(req)
}
@@ -151,7 +153,7 @@ func NewHTTPClient(conf SSOConfig) (*http.Client, error) {
return nil, err
}
if conf.Cert == "" {
- slog.Debug("making http requests without SSO")
+ log.V(1).Info("making http requests without SSO")
}
return &http.Client{Transport: &userAgentTransport{T: &requestLogger{T: transport}}}, nil
}
diff --git a/postgres/Dockerfile b/postgres/Dockerfile
index 1408f2f06..93cf6e257 100644
--- a/postgres/Dockerfile
+++ b/postgres/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f
+FROM debian:trixie-slim@sha256:62bd423aeee912639ca8ee681c2b762cb3e25137e0a92b20be455898a9f034ad
# explicitly set user/group IDs
RUN set -eux; \