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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* arno.uhlig@sap.com julius.clausnitzer@sap.com malte.viering@sap.com marcel.bloecher@sap.com markus.wieland@sap.com p.matthes@sap.com
* arno.uhlig@sap.com julius.clausnitzer@sap.com malte.viering@sap.com marcel.gute@sap.com markus.wieland@sap.com p.matthes@sap.com
6 changes: 6 additions & 0 deletions api/v1alpha1/knowledge_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ type KnowledgeStatus struct {
// +kubebuilder:validation:Optional
LastExtracted metav1.Time `json:"lastExtracted"`

// When the extracted knowledge content last changed.
// Updated only when the Raw data actually changes, not on every reconcile.
// +kubebuilder:validation:Optional
LastContentChange metav1.Time `json:"lastContentChange,omitempty"`

// The raw data behind the extracted knowledge, e.g. a list of features.
// +kubebuilder:validation:Optional
Raw runtime.RawExtension `json:"raw"`
Expand All @@ -111,6 +116,7 @@ type KnowledgeStatus struct {
// +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.schedulingDomain"
// +kubebuilder:printcolumn:name="Created",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Extracted",type="date",JSONPath=".status.lastExtracted"
// +kubebuilder:printcolumn:name="Changed",type="date",JSONPath=".status.lastContentChange"
// +kubebuilder:printcolumn:name="Recency",type="string",JSONPath=".spec.recency"
// +kubebuilder:printcolumn:name="Features",type="integer",JSONPath=".status.rawLength"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
Expand Down
20 changes: 19 additions & 1 deletion api/v1alpha1/reservation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ const (
ReservationTypeFailover ReservationType = "FailoverReservation"
)

// Label keys for Reservation metadata.
// Labels follow Kubernetes naming conventions using reverse-DNS notation
const (
// ===== Common Reservation Labels =====

// LabelReservationType identifies the type of reservation.
// This label is present on all reservations to enable type-based filtering.
LabelReservationType = "reservations.cortex.sap.com/type"

// Reservation type label values
ReservationTypeLabelCommittedResource = "committed-resource"
ReservationTypeLabelFailover = "failover"
)

// CommittedResourceAllocation represents a workload's assignment to a committed resource reservation slot.
// The workload could be a VM (Nova/IronCore), Pod (Kubernetes), or other resource.
type CommittedResourceAllocation struct {
Expand Down Expand Up @@ -79,6 +93,10 @@ type ReservationSpec struct {
// +kubebuilder:validation:Optional
SchedulingDomain string `json:"schedulingDomain,omitempty"`

// AvailabilityZone specifies the availability zone for this reservation, if restricted to a specific AZ.
// +kubebuilder:validation:Optional
AvailabilityZone string `json:"availabilityZone,omitempty"`

// Resources to reserve for this instance.
// +kubebuilder:validation:Optional
Resources map[string]resource.Quantity `json:"resources,omitempty"`
Expand Down Expand Up @@ -166,7 +184,7 @@ type ReservationStatus struct {
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type"
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".metadata.labels['reservations\\.cortex\\.sap\\.com/type']"
// +kubebuilder:printcolumn:name="Host",type="string",JSONPath=".status.host"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"

Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

12 changes: 8 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ func main() {
httpAPIConf := conf.GetConfigOrDie[nova.HTTPAPIConfig]()
nova.NewAPI(httpAPIConf, filterWeigherController).Init(mux)

// Initialize commitments API for LIQUID interface
commitmentsAPI := commitments.NewAPI(multiclusterClient)
commitmentsAPI.Init(mux)

// Detector pipeline controller setup.
novaClient := nova.NewNovaClient()
novaClientConfig := conf.GetConfigOrDie[nova.NovaClientConfig]()
Expand Down Expand Up @@ -456,11 +460,11 @@ func main() {
monitor := reservationscontroller.NewControllerMonitor(multiclusterClient)
metrics.Registry.MustRegister(&monitor)
reservationsControllerConfig := conf.GetConfigOrDie[reservationscontroller.Config]()

if err := (&reservationscontroller.ReservationReconciler{
Client: multiclusterClient,
Scheme: mgr.GetScheme(),
Conf: reservationsControllerConfig,
HypervisorClient: reservationscontroller.NewHypervisorClient(),
Client: multiclusterClient,
Scheme: mgr.GetScheme(),
Conf: reservationsControllerConfig,
}).SetupWithManager(mgr, multiclusterClient); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Reservation")
os.Exit(1)
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/go-gorp/gorp v2.2.0+incompatible
github.com/gophercloud/gophercloud/v2 v2.10.0
github.com/ironcore-dev/ironcore v0.2.4
github.com/majewsky/gg v1.5.0
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
github.com/sapcc/go-bits v0.0.0-20260226170120-c20f89b66c3c
Expand Down Expand Up @@ -36,7 +37,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/logr v1.4.3
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.22.1 // indirect
Expand Down Expand Up @@ -71,7 +72,7 @@ require (
github.com/poy/onpar v0.3.5 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/sapcc/go-api-declarations v1.20.2 // indirect
github.com/sapcc/go-api-declarations v1.20.2
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cobra v1.10.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
Expand Down
2 changes: 1 addition & 1 deletion helm/bundles/cortex-cinder/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner-info:
- "arno.uhlig@sap.com"
- "julius.clausnitzer@sap.com"
- "malte.viering@sap.com"
- "marcel.bloecher@sap.com"
- "marcel.gute@sap.com"
- "markus.wieland@sap.com"
- "p.matthes@sap.com"
support-group: "workload-management"
Expand Down
2 changes: 1 addition & 1 deletion helm/bundles/cortex-crds/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner-info:
- "arno.uhlig@sap.com"
- "julius.clausnitzer@sap.com"
- "malte.viering@sap.com"
- "marcel.bloecher@sap.com"
- "marcel.gute@sap.com"
- "markus.wieland@sap.com"
- "p.matthes@sap.com"
support-group: "workload-management"
Expand Down
2 changes: 1 addition & 1 deletion helm/bundles/cortex-ironcore/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner-info:
- "arno.uhlig@sap.com"
- "julius.clausnitzer@sap.com"
- "malte.viering@sap.com"
- "marcel.bloecher@sap.com"
- "marcel.gute@sap.com"
- "markus.wieland@sap.com"
- "p.matthes@sap.com"
support-group: "workload-management"
Expand Down
2 changes: 1 addition & 1 deletion helm/bundles/cortex-manila/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner-info:
- "arno.uhlig@sap.com"
- "julius.clausnitzer@sap.com"
- "malte.viering@sap.com"
- "marcel.bloecher@sap.com"
- "marcel.gute@sap.com"
- "markus.wieland@sap.com"
- "p.matthes@sap.com"
support-group: "workload-management"
Expand Down
17 changes: 17 additions & 0 deletions helm/bundles/cortex-nova/templates/knowledges_kvm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
---
apiVersion: cortex.cloud/v1alpha1
kind: Knowledge
metadata:
name: flavor-groups
spec:
schedulingDomain: nova
extractor:
name: flavor_groups
recency: "5m"
description: |
This knowledge extracts flavor groups from Nova flavors based on the
hw_version extra_spec. It identifies all flavors belonging to each group
and determines the largest flavor for reservation slot sizing.
dependencies:
datasources:
- name: nova-flavors
---
apiVersion: cortex.cloud/v1alpha1
kind: Knowledge
metadata:
name: kvm-libvirt-domain-cpu-steal-pct
spec:
Expand Down
7 changes: 5 additions & 2 deletions helm/bundles/cortex-nova/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner-info:
- "arno.uhlig@sap.com"
- "julius.clausnitzer@sap.com"
- "malte.viering@sap.com"
- "marcel.bloecher@sap.com"
- "marcel.gute@sap.com"
- "markus.wieland@sap.com"
- "p.matthes@sap.com"
support-group: "workload-management"
Expand Down Expand Up @@ -114,8 +114,12 @@ cortex-scheduling-controllers:
- nova-pipeline-controllers
- nova-deschedulings-executor
- explanation-controller
- reservations-controller
enabledTasks:
- nova-decisions-cleanup-task
# Endpoints configuration for reservations controller
endpoints:
novaExternalScheduler: "http://localhost:8080/scheduler/nova/external"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would call localhost, as in the container itself, right? This works as long as the scheduling controllers are initialized within the same pod. Maybe we can make this more robust by 1. adding a comment indicating this dependency or 2. using kubernetes dns to resolve to the correct service, something like cortex-nova-scheduler.svc....


cortex-knowledge-controllers:
<<: *cortex
Expand All @@ -134,7 +138,6 @@ cortex-knowledge-controllers:
- datasource-controllers
- knowledge-controllers
- kpis-controller
- reservations-controller
enabledTasks:
- commitments-sync-task

Expand Down
2 changes: 1 addition & 1 deletion helm/bundles/cortex-pods/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ owner-info:
- "arno.uhlig@sap.com"
- "julius.clausnitzer@sap.com"
- "malte.viering@sap.com"
- "marcel.bloecher@sap.com"
- "marcel.gute@sap.com"
- "markus.wieland@sap.com"
- "p.matthes@sap.com"
support-group: "workload-management"
Expand Down
9 changes: 9 additions & 0 deletions helm/library/cortex/files/crds/cortex.cloud_knowledges.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ spec:
- jsonPath: .status.lastExtracted
name: Extracted
type: date
- jsonPath: .status.lastContentChange
name: Changed
type: date
- jsonPath: .spec.recency
name: Recency
type: string
Expand Down Expand Up @@ -248,6 +251,12 @@ spec:
- type
type: object
type: array
lastContentChange:
description: |-
When the extracted knowledge content last changed.
Updated only when the Raw data actually changes, not on every reconcile.
format: date-time
type: string
lastExtracted:
description: When the knowledge was last successfully extracted.
format: date-time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec:
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.type
- jsonPath: .metadata.labels['reservations\.cortex\.sap\.com/type']
name: Type
type: string
- jsonPath: .status.host
Expand Down Expand Up @@ -49,6 +49,10 @@ spec:
spec:
description: spec defines the desired state of Reservation
properties:
availabilityZone:
description: AvailabilityZone specifies the availability zone for
this reservation, if restricted to a specific AZ.
type: string
committedResourceReservation:
description: |-
CommittedResourceReservation contains fields specific to committed resource reservations.
Expand Down
20 changes: 20 additions & 0 deletions internal/knowledge/extractor/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package extractor

import (
"context"
"encoding/json"
"reflect"
"time"

"github.com/cobaltcore-dev/cortex/api/v1alpha1"
Expand Down Expand Up @@ -202,9 +204,27 @@ func (r *KnowledgeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
Reason: "KnowledgeExtracted",
Message: "knowledge extracted successfully",
})

// Check if content actually changed by comparing deserialized data structures.
// This avoids false positives from JSON serialization non-determinism (e.g., map key ordering).
contentChanged := true
if len(knowledge.Status.Raw.Raw) > 0 {
var oldData, newData interface{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use any instead of interface{}

if err := json.Unmarshal(knowledge.Status.Raw.Raw, &oldData); err == nil {
if err := json.Unmarshal(raw.Raw, &newData); err == nil {
contentChanged = !reflect.DeepEqual(oldData, newData)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool. I recently stumbled across k8s.io/apimachinery/pkg/api/equality which exposes similar functionality. I wonder if this is easier to work with runtime.RawExtension https://github.com/ironcore-dev/network-operator/blob/b69b373b7513fadb47823c9e280fe915cc58f07e/internal/controller/core/bgp_controller.go#L186

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice pointer, but lacks the same issue that content changes are detected but there was none, so false positives.

}

knowledge.Status.Raw = raw
knowledge.Status.LastExtracted = metav1.NewTime(time.Now())
knowledge.Status.RawLength = len(features)

if contentChanged {
log.Info("content of knowledge has changed", "name", knowledge.Name)
knowledge.Status.LastContentChange = metav1.NewTime(time.Now())
}
patch := client.MergeFrom(old)
if err := r.Status().Patch(ctx, knowledge, patch); err != nil {
log.Error(err, "failed to patch knowledge status")
Expand Down
Loading
Loading