From 34a9dc2680ceda472bf5a1101906d3d6c87d1381 Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Sun, 23 Nov 2025 21:31:39 +0100 Subject: [PATCH 1/8] start refactoring crds --- api/v1alpha1/common.go | 12 ++ api/v1alpha1/datasource_types.go | 298 ----------------------------- api/v1alpha1/decision_types.go | 24 +-- api/v1alpha1/descheduling_types.go | 2 +- api/v1alpha1/knowledge_types.go | 2 +- api/v1alpha1/kpi_types.go | 2 +- api/v1alpha1/pipeline_types.go | 76 ++++---- api/v1alpha1/reservation_types.go | 104 ++++++---- api/v1alpha1/step_types.go | 2 +- docs/architecture.md | 2 +- docs/develop.md | 3 +- 11 files changed, 132 insertions(+), 395 deletions(-) create mode 100644 api/v1alpha1/common.go delete mode 100644 api/v1alpha1/datasource_types.go diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go new file mode 100644 index 000000000..a5dbb5f73 --- /dev/null +++ b/api/v1alpha1/common.go @@ -0,0 +1,12 @@ +package v1alpha1 + +// SchedulingDomain reflects the logical domain for scheduling, such as nova, cinder, manila. +type SchedulingDomain string + +const ( + // SchedulingDomainNova indicates an OpenStack Nova (compute) scheduling domain. + SchedulingDomainNova SchedulingDomain = "nova" + + // SchedulingDomainCinder ... + SchedulingDomainCinder SchedulingDomain = "cinder" +) diff --git a/api/v1alpha1/datasource_types.go b/api/v1alpha1/datasource_types.go deleted file mode 100644 index c994a2424..000000000 --- a/api/v1alpha1/datasource_types.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2025 SAP SE -// SPDX-License-Identifier: Apache-2.0 - -package v1alpha1 - -import ( - "errors" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -var ( - // Some datasources may depend on other datasources to be present. - // If these aren't yet available, this will be the returned error. - ErrWaitingForDependencyDatasource = errors.New("waiting for dependency datasource to become available") -) - -type PrometheusDatasource struct { - // The query to use to fetch the metric. - Query string `json:"query"` - // Especially when a more complex query is used, we need an alias - // under which the table will be stored in the database. - // Additionally, this alias is used to reference the metric in the - // feature extractors as dependency. - Alias string `json:"alias"` - // The type of the metric, mapping directly to a metric model supported - // by cortex. Note that the metrics are fetched as time series, not instant. - Type string `json:"type"` - - // Time range to query the data for. - // +kubebuilder:default="2419200s" - TimeRange metav1.Duration `json:"timeRange"` - // The interval at which to query the data. - // +kubebuilder:default="86400s" - Interval metav1.Duration `json:"interval"` - // The resolution of the data. - // +kubebuilder:default="43200s" - Resolution metav1.Duration `json:"resolution"` - - // Secret containing the following keys: - // - "url": The prometheus URL. - SecretRef corev1.SecretReference `json:"secretRef"` -} - -type NovaDatasourceType string - -const ( - NovaDatasourceTypeServers NovaDatasourceType = "servers" - NovaDatasourceTypeDeletedServers NovaDatasourceType = "deletedServers" - NovaDatasourceTypeHypervisors NovaDatasourceType = "hypervisors" - NovaDatasourceTypeFlavors NovaDatasourceType = "flavors" - NovaDatasourceTypeMigrations NovaDatasourceType = "migrations" - NovaDatasourceTypeAggregates NovaDatasourceType = "aggregates" -) - -type NovaDatasource struct { - // The type of resource to sync. - Type NovaDatasourceType `json:"type"` - // Time frame in minutes for the changes-since parameter when fetching - // deleted servers. Set if the Type is "deletedServers". - DeletedServersChangesSinceMinutes *int `json:"deletedServersChangesSinceMinutes,omitempty"` -} - -type PlacementDatasourceType string - -const ( - PlacementDatasourceTypeResourceProviders PlacementDatasourceType = "resourceProviders" - PlacementDatasourceTypeResourceProviderInventoryUsages PlacementDatasourceType = "resourceProviderInventoryUsages" - PlacementDatasourceTypeResourceProviderTraits PlacementDatasourceType = "resourceProviderTraits" -) - -type PlacementDatasource struct { - // The type of resource to sync. - Type PlacementDatasourceType `json:"type"` -} - -type ManilaDatasourceType string - -const ( - ManilaDatasourceTypeStoragePools ManilaDatasourceType = "storagePools" -) - -type ManilaDatasource struct { - // The type of resource to sync. - Type ManilaDatasourceType `json:"type"` -} - -type IdentityDatasourceType string - -const ( - IdentityDatasourceTypeProjects IdentityDatasourceType = "projects" - IdentityDatasourceTypeDomains IdentityDatasourceType = "domains" -) - -type IdentityDatasource struct { - // The type of resource to sync. - Type IdentityDatasourceType `json:"type"` -} - -type LimesDatasourceType string - -const ( - LimesDatasourceTypeProjectCommitments LimesDatasourceType = "projectCommitments" -) - -type LimesDatasource struct { - // The type of resource to sync. - Type LimesDatasourceType `json:"type"` -} - -type CinderDatasourceType string - -const ( - CinderDatasourceTypeStoragePools CinderDatasourceType = "storagePools" -) - -type CinderDatasource struct { - // The type of resource to sync. - Type CinderDatasourceType `json:"type"` -} - -type OpenStackDatasourceType string - -const ( - // OpenStackDatasourceTypeNova indicates a Nova datasource. - OpenStackDatasourceTypeNova OpenStackDatasourceType = "nova" - // OpenStackDatasourceTypePlacement indicates a Placement datasource. - OpenStackDatasourceTypePlacement OpenStackDatasourceType = "placement" - // OpenStackDatasourceTypeManila indicates a Manila datasource. - OpenStackDatasourceTypeManila OpenStackDatasourceType = "manila" - // OpenStackDatasourceTypeIdentity indicates an Identity datasource. - OpenStackDatasourceTypeIdentity OpenStackDatasourceType = "identity" - // OpenStackDatasourceTypeLimes indicates a Limes datasource. - OpenStackDatasourceTypeLimes OpenStackDatasourceType = "limes" - // OpenStackDatasourceTypeCinder indicates a Cinder datasource. - OpenStackDatasourceTypeCinder OpenStackDatasourceType = "cinder" -) - -type OpenStackDatasource struct { - // The type of the OpenStack datasource. - Type OpenStackDatasourceType `json:"type"` - - // Datasource for openstack nova. - // Only required if Type is "nova". - // +kubebuilder:validation:Optional - Nova NovaDatasource `json:"nova"` - // Datasource for openstack placement. - // Only required if Type is "placement". - // +kubebuilder:validation:Optional - Placement PlacementDatasource `json:"placement"` - // Datasource for openstack manila. - // Only required if Type is "manila". - // +kubebuilder:validation:Optional - Manila ManilaDatasource `json:"manila"` - // Datasource for openstack identity. - // Only required if Type is "identity". - // +kubebuilder:validation:Optional - Identity IdentityDatasource `json:"identity"` - // Datasource for openstack limes. - // Only required if Type is "limes". - // +kubebuilder:validation:Optional - Limes LimesDatasource `json:"limes"` - // Datasource for openstack cinder. - // Only required if Type is "cinder". - // +kubebuilder:validation:Optional - Cinder CinderDatasource `json:"cinder"` - - // How often to sync the datasource. - // +kubebuilder:default="60s" - SyncInterval metav1.Duration `json:"syncInterval"` - - // Keystone credentials secret ref for authenticating with openstack. - // The secret should contain the following keys: - // - "availability": The service availability, e.g. "public", "internal", or "admin". - // - "url": The keystone auth URL. - // - "username": The keystone username. - // - "password": The keystone password. - // - "userDomainName": The keystone user domain name. - // - "projectName": The keystone project name. - // - "projectDomainName": The keystone project domain name. - SecretRef corev1.SecretReference `json:"secretRef"` -} - -type DatasourceType string - -const ( - // DatasourceTypePrometheus indicates a Prometheus datasource. - DatasourceTypePrometheus DatasourceType = "prometheus" - // DatasourceTypeOpenStack indicates an OpenStack datasource. - DatasourceTypeOpenStack DatasourceType = "openstack" -) - -type DatasourceSpec struct { - // The operator by which this datasource should be synced. - Operator string `json:"operator,omitempty"` - - // If given, configures a Prometheus datasource to fetch. - // Type must be set to "prometheus" if this is used. - // +kubebuilder:validation:Optional - Prometheus PrometheusDatasource `json:"prometheus"` - // If given, configures an OpenStack datasource to fetch. - // Type must be set to "openstack" if this is used. - // +kubebuilder:validation:Optional - OpenStack OpenStackDatasource `json:"openstack,omitempty"` - - // The type of the datasource. - Type DatasourceType `json:"type"` - - // Database credentials to use for the datasource. - // The secret should contain the following keys: - // - "username": The database username. - // - "password": The database password. - // - "host": The database host. - // - "port": The database port. - // - "database": The database name. - DatabaseSecretRef corev1.SecretReference `json:"databaseSecretRef"` - - // Kubernetes secret ref for an optional sso certificate to access the host. - // The secret should contain two keys: "cert" and "key". - // +kubebuilder:validation:Optional - SSOSecretRef *corev1.SecretReference `json:"ssoSecretRef,omitempty"` -} - -const ( - // Something went wrong during the syncing of the datasource. - DatasourceConditionError = "Error" - // The datasource is waiting for a dependency datasource to become available. - DatasourceConditionWaiting = "Waiting" -) - -type DatasourceStatus struct { - // When the datasource was last successfully synced. - LastSynced metav1.Time `json:"lastSynced,omitempty"` - // The number of objects currently stored for this datasource. - NumberOfObjects int64 `json:"numberOfObjects,omitempty"` - // The time it took to perform the last sync. - Took metav1.Duration `json:"took"` - // Planned time for the next sync. - NextSyncTime metav1.Time `json:"nextSyncTime,omitempty"` - - // The current status conditions of the datasource. - // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` -} - -// Helper function to check if the datasource is ready. -func (s *DatasourceStatus) IsReady() bool { - if meta.IsStatusConditionTrue(s.Conditions, DatasourceConditionError) { - return false - } - if meta.IsStatusConditionTrue(s.Conditions, DatasourceConditionWaiting) { - return false - } - return s.NumberOfObjects > 0 -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:resource:scope=Cluster -// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type" -// +kubebuilder:printcolumn:name="Operator",type="string",JSONPath=".spec.operator" -// +kubebuilder:printcolumn:name="Created",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:printcolumn:name="Synced",type="date",JSONPath=".status.lastSynced" -// +kubebuilder:printcolumn:name="Took",type="string",JSONPath=".status.took" -// +kubebuilder:printcolumn:name="Next",type="string",JSONPath=".status.nextSyncTime" -// +kubebuilder:printcolumn:name="Objects",type="integer",JSONPath=".status.numberOfObjects" - -// Datasource is the Schema for the datasources API -type Datasource struct { - metav1.TypeMeta `json:",inline"` - - // metadata is a standard object metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` - - // spec defines the desired state of Datasource - // +required - Spec DatasourceSpec `json:"spec"` - - // status defines the observed state of Datasource - // +optional - Status DatasourceStatus `json:"status,omitempty,omitzero"` -} - -// +kubebuilder:object:root=true - -// DatasourceList contains a list of Datasource -type DatasourceList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Datasource `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Datasource{}, &DatasourceList{}) -} diff --git a/api/v1alpha1/decision_types.go b/api/v1alpha1/decision_types.go index ea71c60ff..3c85ea105 100644 --- a/api/v1alpha1/decision_types.go +++ b/api/v1alpha1/decision_types.go @@ -6,23 +6,7 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// The type of decision. -type DecisionType string - -const ( - // The decision was created by the nova external scheduler call. - // Usually we refer to this as nova initial placement, it also includes - // migrations or resizes. - DecisionTypeNovaServer DecisionType = "nova-server" - // The decision was created by the cinder external scheduler call. - DecisionTypeCinderVolume DecisionType = "cinder-volume" - // The decision was created by the manila external scheduler call. - DecisionTypeManilaShare DecisionType = "manila-share" - // The decision was created by spawning an ironcore machine. - DecisionTypeIroncoreMachine DecisionType = "ironcore-machine" + "k8s.io/apimachinery/pkg/runtime" ) type DecisionSpec struct { @@ -39,8 +23,10 @@ type DecisionSpec struct { // This can be used to correlate multiple decisions for the same resource. ResourceID string `json:"resourceID"` - // The type of decision, indicating what has initiated this decision. - Type DecisionType `json:"type"` + // The domain of the decision, indicating what has initiated this decision. + Domain SchedulingDomain `json:"type"` + + // TODO: Avoid using RawExtension! // If the type is "nova", this field contains the raw nova decision request. // +kubebuilder:validation:Optional NovaRaw *runtime.RawExtension `json:"novaRaw,omitempty"` diff --git a/api/v1alpha1/descheduling_types.go b/api/v1alpha1/descheduling_types.go index 060a7c56d..7892e5d8b 100644 --- a/api/v1alpha1/descheduling_types.go +++ b/api/v1alpha1/descheduling_types.go @@ -60,7 +60,7 @@ type DeschedulingStatus struct { Phase DeschedulingStatusPhase `json:"phase"` // The current status conditions of the descheduling. // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` // The name of the compute host where the VM was rescheduled to. NewHost string `json:"newHost,omitempty"` // The type of host where the VM was rescheduled to. diff --git a/api/v1alpha1/knowledge_types.go b/api/v1alpha1/knowledge_types.go index cba0d026c..f08499d2c 100644 --- a/api/v1alpha1/knowledge_types.go +++ b/api/v1alpha1/knowledge_types.go @@ -127,7 +127,7 @@ type KnowledgeStatus struct { // The current status conditions of the knowledge. // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } // Helper function to check if the knowledge is ready. diff --git a/api/v1alpha1/kpi_types.go b/api/v1alpha1/kpi_types.go index 59dee1b74..274aa5ab5 100644 --- a/api/v1alpha1/kpi_types.go +++ b/api/v1alpha1/kpi_types.go @@ -6,7 +6,7 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // Dependencies required for extracting the kpi. diff --git a/api/v1alpha1/pipeline_types.go b/api/v1alpha1/pipeline_types.go index 20495b150..e3db8b616 100644 --- a/api/v1alpha1/pipeline_types.go +++ b/api/v1alpha1/pipeline_types.go @@ -4,53 +4,52 @@ package v1alpha1 import ( - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type StepInPipeline struct { - // Reference to the step. - Ref corev1.ObjectReference `json:"ref"` - // Whether this step is mandatory for the pipeline to be runnable. - // +kubebuilder:default=true - Mandatory bool `json:"mandatory"` +// Filter is a hard constraints to ensure valid placement and scheduling and must be executed. +type Filter struct { } -type PipelineType string - -const ( - // Pipeline containing filter-weigher steps for initial placement, - // migration, etc. of instances. - PipelineTypeFilterWeigher PipelineType = "filter-weigher" - // Pipeline containing descheduler steps for generating descheduling - // recommendations. - PipelineTypeDescheduler PipelineType = "descheduler" -) +// Weigher is a scheduling objective and should be executed to achieve optimal placement and scheduling. +type Weigher struct { +} type PipelineSpec struct { - // The operator by which this pipeline should be handled. - Operator string `json:"operator,omitempty"` - // An optional description of the pipeline. - // +kubebuilder:validation:Optional - Description string `json:"description,omitempty"` - // If this pipeline should create decision objects. - // When this is false, the pipeline will still process requests. - // +kubebuilder:default=false - CreateDecisions bool `json:"createDecisions,omitempty"` - // The type of the pipeline. - Type PipelineType `json:"type"` - // The ordered list of steps that make up this pipeline. - Steps []StepInPipeline `json:"steps,omitempty"` + // Filters ... + Filters []Filter `json:"filters"` + + // Weighers ... + Weighers []Weigher `json:"weighers"` } +type PipelineConditionType string + const ( - // Something went wrong during the pipeline reconciliation. - PipelineConditionError = "Error" + // PipelineReady reflects the ready status of the pipeline. + PipelineReady PipelineConditionType = "Ready" ) +type PipelineCondition struct { + // Type of pipelne condition. + Type PipelineConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status metav1.ConditionStatus `json:"status"` + // Last time we got an update on a given condition. + // +optional + LastHeartbeatTime metav1.Time `json:"lastHeartbeatTime,omitempty"` + // Last time the condition transit from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // (brief) reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` +} + type PipelineStatus struct { - // Whether the pipeline is ready to be used. - Ready bool `json:"ready"` // The total number of steps configured in the pipeline. TotalSteps int `json:"totalSteps"` // The number of steps that are ready. @@ -60,7 +59,7 @@ type PipelineStatus struct { StepsReadyFrac string `json:"stepsReadyFrac,omitempty"` // The current status conditions of the pipeline. // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + Conditions []PipelineCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } // +kubebuilder:object:root=true @@ -74,11 +73,8 @@ type PipelineStatus struct { // Pipeline is the Schema for the decisions API type Pipeline struct { - metav1.TypeMeta `json:",inline"` - - // metadata is a standard object metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` // spec defines the desired state of Pipeline // +required diff --git a/api/v1alpha1/reservation_types.go b/api/v1alpha1/reservation_types.go index 03386d932..8b96a3e04 100644 --- a/api/v1alpha1/reservation_types.go +++ b/api/v1alpha1/reservation_types.go @@ -4,19 +4,44 @@ package v1alpha1 import ( - "k8s.io/apimachinery/pkg/api/resource" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Additional specifications needed to place the reservation. -type ReservationSchedulerSpec struct { - // If the type of scheduler is cortex-nova, this field will contain additional - // information used by cortex-nova to place the instance. - CortexNova *ReservationSchedulerSpecCortexNova `json:"cortexNova,omitempty"` +// ReservationSpec defines the desired state of Reservation. +type ReservationSpec struct { + // Domain reflects the logical scheduling domain of this reservation, such as nova, cinder, manila. + Domain SchedulingDomain `json:"type"` + + // Resources to be reserved for this reservation request. + Resources corev1.ResourceList `json:"requests"` + + // StartTime reflects the start timestamp for the reservation. + StartTime metav1.Time `json:"startTime,omitempty"` + + // EndTime reflects the expiry timestamp for the reservation. + // TODO: Alternative Duration? + EndTime metav1.Time `json:"endTime,omitempty"` + + // ActiveTime reflects the time the reservation became used. + ActiveTime metav1.Time `json:"activeTime,omitempty"` + + // ProjectID ... + ProjectID string `json:"projectID,omitempty"` + + // Selector ... + Selector *metav1.LabelSelector `json:"selector,omitempty"` + + // Affinity/Anti-Affinity + + // Nova will contain additional information used by cortex-nova to place the instance. + // The field may be empty for non-Nova requests + Nova *ReservationSpecNova `json:"cortexNova,omitempty"` } -// Additional specifications needed by cortex-nova to place the instance. -type ReservationSchedulerSpecCortexNova struct { +// ReservationSpecNova is an additional specification needed by OpenStack Nova to place the instance. +// TODO: Generalize? +type ReservationSpecNova struct { // The project ID to reserve for. ProjectID string `json:"projectID,omitempty"` // The domain ID to reserve for. @@ -27,31 +52,28 @@ type ReservationSchedulerSpecCortexNova struct { FlavorExtraSpecs map[string]string `json:"flavorExtraSpecs,omitempty"` } -// ReservationSpec defines the desired state of Reservation. -type ReservationSpec struct { - // A remark that can be used to identify the creator of the reservation. - // This can be used to clean up reservations synced from external systems - // without touching reservations created manually or by other systems. - Creator string `json:"creator,omitempty"` - // Specification of the scheduler that will handle the reservation. - Scheduler ReservationSchedulerSpec `json:"scheduler,omitempty"` - // Resources requested to reserve for this instance. - Requests map[string]resource.Quantity `json:"requests,omitempty"` -} - -// The phase in which the reservation is. +// ReservationStatusPhase is a high-level summary of the reservation lifecycle. type ReservationStatusPhase string const ( - // The reservation has been placed and is considered during scheduling. - ReservationStatusPhaseActive ReservationStatusPhase = "active" - // The reservation could not be fulfilled. - ReservationStatusPhaseFailed ReservationStatusPhase = "failed" + // ReservationStatusPhasePending reflects a not yet scheduled reservation. + ReservationStatusPhasePending ReservationStatusPhase = "Pending" + + // ReservationStatusPhaseActive indicates that the reservation has been successfully scheduled. + ReservationStatusPhaseActive ReservationStatusPhase = "Active" + + // ReservationStatusPhaseFailed indicated that the reservation could not be honored. + ReservationStatusPhaseFailed ReservationStatusPhase = "Failed" + + // ReservationStatusPhaseExpired reflects a reservation past its lifetime ready for garbage collection. + ReservationStatusPhaseExpired ReservationStatusPhase = "Expired" ) +type ReservationConditionType string + const ( - // Something went wrong during the handling of the reservation. - ReservationConditionError = "Error" + // ReservationReady reflects the ready status during the handling of the reservation. + ReservationReady = "Ready" ) // ReservationStatus defines the observed state of Reservation. @@ -60,11 +82,30 @@ type ReservationStatus struct { Phase ReservationStatusPhase `json:"phase,omitempty"` // The current status conditions of the reservation. // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + Conditions []ReservationCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` // The name of the compute host that was allocated. Host string `json:"host"` } +type ReservationCondition struct { + // Type of reservation condition. + Type ReservationConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status metav1.ConditionStatus `json:"status"` + // Last time we got an update on a given condition. + // +optional + LastHeartbeatTime metav1.Time `json:"lastHeartbeatTime,omitempty"` + // Last time the condition transit from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // (brief) reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` +} + // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster @@ -76,8 +117,7 @@ type Reservation struct { metav1.TypeMeta `json:",inline"` // metadata is a standard object metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + metav1.ObjectMeta `json:"metadata,omitempty"` // spec defines the desired state of Reservation // +required @@ -85,12 +125,12 @@ type Reservation struct { // status defines the observed state of Reservation // +optional - Status ReservationStatus `json:"status,omitempty,omitzero"` + Status ReservationStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true -// ReservationList contains a list of Reservation +// ReservationList contains a list of Reservation objects type ReservationList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/api/v1alpha1/step_types.go b/api/v1alpha1/step_types.go index d4866fc31..25ad5198d 100644 --- a/api/v1alpha1/step_types.go +++ b/api/v1alpha1/step_types.go @@ -91,7 +91,7 @@ type StepStatus struct { KnowledgesReadyFrac string `json:"knowledgesReadyFrac,omitempty"` // The current status conditions of the step. // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } // +kubebuilder:object:root=true diff --git a/docs/architecture.md b/docs/architecture.md index bf1d51bf6..443a88d6f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture Guide -This guide provides an overview of the Cortex architecture, its components, and how they interact. +This guide provides an overview of Cortex' architecture, its components, and how they interact. ## Architecture Decision Records (ADRs) diff --git a/docs/develop.md b/docs/develop.md index 5b090c889..a172bb9e8 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -11,9 +11,10 @@ The following snippet provides an overview: . ├── api/ │ ├── / # CRD definitions and webhooks -│ └── delegation/ # Delegation API definitions (external scheduler API) +│ └── delegation/ # Delegation API definitions (external scheduler REST API) ├── config/ # Helm and Kustomize bases, such as CRDs ├── dist/ # Helm chart generated by kubebuilder helm plugin from bases in config/ +├── docs/ # Documentation for developers and users ├── bundles/ # Umbrella Helm charts for easily deploying Cortex ├── internal/ # Project-internal code │ ├── scheduling # Scheduling & workload placement related code From 7f1830731e43acf7078e0ea0f4c8d25fa89d95ed Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Mon, 24 Nov 2025 14:47:56 +0100 Subject: [PATCH 2/8] continue --- api/v1alpha1/README.md | 44 ++++++++++ api/v1alpha1/common.go | 1 + api/v1alpha1/kpi_types.go | 2 +- api/v1alpha1/pipeline_types.go | 66 +++++++++++---- api/v1alpha1/reservation_types.go | 4 +- api/v1alpha1/step_types.go | 134 ------------------------------ 6 files changed, 98 insertions(+), 153 deletions(-) create mode 100644 api/v1alpha1/README.md delete mode 100644 api/v1alpha1/step_types.go diff --git a/api/v1alpha1/README.md b/api/v1alpha1/README.md new file mode 100644 index 000000000..df9034448 --- /dev/null +++ b/api/v1alpha1/README.md @@ -0,0 +1,44 @@ +```yaml +apiVersion: cortex.cloud/v1alpha1 +kind: Pipeline +metadata: + name: nova-general-purpose +spec: + # Ordered filters must be completed successfully in order to ensure valid placement and scheduling. + filters: + - name: + description: + params: + a: b + # Ordered weighers should be completed successfully in order to ensure optimal placement and scheduling. + weighers: + - name: + description: + params: + c: d +``` + + +```yaml +apiVersion: cortex.cloud/v1alpha1 +kind: Reservation +metadata: + name: -hana +spec: + resources: + cpu: 1 + memory: 1TiB + + domain: nova + startTime: + endTime: + activeTime: + + projectID: + + selector: + isNUMAAlignedHost: "true" + + nova: + // everything nova we can't generalize goes here +``` diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index a5dbb5f73..636503b96 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -1,6 +1,7 @@ package v1alpha1 // SchedulingDomain reflects the logical domain for scheduling, such as nova, cinder, manila. +// TODO: Or rename to type to avoid naming clash with openstack domains. type SchedulingDomain string const ( diff --git a/api/v1alpha1/kpi_types.go b/api/v1alpha1/kpi_types.go index 274aa5ab5..d1f6723a4 100644 --- a/api/v1alpha1/kpi_types.go +++ b/api/v1alpha1/kpi_types.go @@ -9,7 +9,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// Dependencies required for extracting the kpi. +// KPIDependenciesSpec is required for extracting the kpi. // If provided, all datasources and knowledges must have the same // database secret reference so the kpi can be joined across multiple // database tables. diff --git a/api/v1alpha1/pipeline_types.go b/api/v1alpha1/pipeline_types.go index e3db8b616..6b2a48ef2 100644 --- a/api/v1alpha1/pipeline_types.go +++ b/api/v1alpha1/pipeline_types.go @@ -7,20 +7,57 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Filter is a hard constraints to ensure valid placement and scheduling and must be executed. -type Filter struct { -} +type ( + // FilterSpec is a hard constraints to ensure valid placement and scheduling and must be executed. + FilterSpec struct { + // Name ... + Name string `json:"name"` -// Weigher is a scheduling objective and should be executed to achieve optimal placement and scheduling. -type Weigher struct { -} + // Description ... + Description string `json:"description,omitempty"` + + // Params ... + Params map[string]string `json:"params,omitempty"` + } + + FilterStatus struct { + // Name ... + Name string `json:"name"` + + Status string `json:"status"` + } +) + +type ( + // WeigherSpec is a scheduling objective and should be executed to achieve optimal placement and scheduling. + WeigherSpec struct { + // Name ... + Name string `json:"name"` + + // Description ... + Description string `json:"description,omitempty"` + + // Params ... + Params map[string]string `json:"params,omitempty"` + + // Multiplier ... + Multiplier *float64 `json:"multiplier,omitempty"` + } + + WeigherStatus struct { + // Name ... + Name string `json:"name"` + + Status string `json:"status"` + } +) type PipelineSpec struct { // Filters ... - Filters []Filter `json:"filters"` + Filters []FilterSpec `json:"filters,omitempty"` // Weighers ... - Weighers []Weigher `json:"weighers"` + Weighers []WeigherSpec `json:"weighers,omitempty"` } type PipelineConditionType string @@ -50,14 +87,11 @@ type PipelineCondition struct { } type PipelineStatus struct { - // The total number of steps configured in the pipeline. - TotalSteps int `json:"totalSteps"` - // The number of steps that are ready. - ReadySteps int `json:"readySteps"` - // An overview of the readiness of the steps in the pipeline. - // Format: "ReadySteps / TotalSteps steps ready". - StepsReadyFrac string `json:"stepsReadyFrac,omitempty"` - // The current status conditions of the pipeline. + Filters []FilterStatus `json:"filters,omitempty"` + + Weighers []WeigherStatus `json:"weighers,omitempty"` + + // Conditions describe the status of the pipeline. // +kubebuilder:validation:Optional Conditions []PipelineCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } diff --git a/api/v1alpha1/reservation_types.go b/api/v1alpha1/reservation_types.go index 8b96a3e04..83ee8c9e7 100644 --- a/api/v1alpha1/reservation_types.go +++ b/api/v1alpha1/reservation_types.go @@ -11,7 +11,7 @@ import ( // ReservationSpec defines the desired state of Reservation. type ReservationSpec struct { // Domain reflects the logical scheduling domain of this reservation, such as nova, cinder, manila. - Domain SchedulingDomain `json:"type"` + Domain SchedulingDomain `json:"domain"` // Resources to be reserved for this reservation request. Resources corev1.ResourceList `json:"requests"` @@ -36,7 +36,7 @@ type ReservationSpec struct { // Nova will contain additional information used by cortex-nova to place the instance. // The field may be empty for non-Nova requests - Nova *ReservationSpecNova `json:"cortexNova,omitempty"` + Nova *ReservationSpecNova `json:"nova,omitempty"` } // ReservationSpecNova is an additional specification needed by OpenStack Nova to place the instance. diff --git a/api/v1alpha1/step_types.go b/api/v1alpha1/step_types.go deleted file mode 100644 index 25ad5198d..000000000 --- a/api/v1alpha1/step_types.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2025 SAP SE -// SPDX-License-Identifier: Apache-2.0 - -package v1alpha1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -type DisabledValidationsSpec struct { - // Whether to validate that no subjects are removed or added from the scheduler - // step. This should only be disabled for scheduler steps that remove subjects. - // Thus, if no value is provided, the default is false. - SameSubjectNumberInOut bool `json:"sameSubjectNumberInOut,omitempty"` - // Whether to validate that, after running the step, there are remaining subjects. - // This should only be disabled for scheduler steps that are expected to - // remove all subjects. - SomeSubjectsRemain bool `json:"someSubjectsRemain,omitempty"` -} - -type StepType string - -const ( - // Step for assigning weights to hosts. - StepTypeWeigher StepType = "weigher" - // Step for filtering hosts. - StepTypeFilter StepType = "filter" - // Step for generating descheduling recommendations. - StepTypeDescheduler StepType = "descheduler" -) - -type WeigherSpec struct { - // The validations to disable for this step. If none are provided, all - // applied validations are enabled. - // +kubebuilder:validation:Optional - DisabledValidations DisabledValidationsSpec `json:"disabledValidations,omitempty"` -} - -type StepSpec struct { - // The operator by which this step should be executed. - Operator string `json:"operator,omitempty"` - - // The type of the scheduler step. - Type StepType `json:"type"` - // If the type is "weigher", this contains additional configuration for it. - // +kubebuilder:validation:Optional - Weigher *WeigherSpec `json:"weigher,omitempty"` - - // The name of the scheduler step in the cortex implementation. - Impl string `json:"impl"` - // Additional configuration for the extractor that can be used - // +kubebuilder:validation:Optional - Opts runtime.RawExtension `json:"opts,omitempty"` - // Knowledges this step depends on to be ready. - // +kubebuilder:validation:Optional - Knowledges []corev1.ObjectReference `json:"knowledges,omitempty"` - // Additional description of the step which helps understand its purpose - // and decisions made by it. - // +kubebuilder:validation:Optional - Description string `json:"description,omitempty"` - - // If needed, database credentials for fetching data from the database. - // The secret should contain the following keys: - // - "username": The database username. - // - "password": The database password. - // - "host": The database host. - // - "port": The database port. - // - "database": The database name. - // Note: this field will be removed in the future when db access in scheduler - // steps is no longer needed. - // +kubebuilder:validation:Optional - DatabaseSecretRef *corev1.SecretReference `json:"databaseSecretRef"` -} - -const ( - // Something went wrong during the step reconciliation. - StepConditionError = "Error" -) - -type StepStatus struct { - // If the step is ready to be executed. - Ready bool `json:"ready"` - // How many knowledges have been extracted. - ReadyKnowledges int `json:"readyKnowledges"` - // Total number of knowledges configured. - TotalKnowledges int `json:"totalKnowledges"` - // "ReadyKnowledges / TotalKnowledges ready" as a human-readable string - // or "ready" if there are no knowledges configured. - KnowledgesReadyFrac string `json:"knowledgesReadyFrac,omitempty"` - // The current status conditions of the step. - // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:resource:scope=Cluster -// +kubebuilder:printcolumn:name="Created",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:printcolumn:name="Operator",type="string",JSONPath=".spec.operator" -// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type" -// +kubebuilder:printcolumn:name="Ready",type="boolean",JSONPath=".status.ready" -// +kubebuilder:printcolumn:name="Knowledges",type="string",JSONPath=".status.knowledgesReadyFrac" - -// Step is the Schema for the deschedulings API -type Step struct { - metav1.TypeMeta `json:",inline"` - - // metadata is a standard object metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` - - // spec defines the desired state of Step - // +required - Spec StepSpec `json:"spec"` - - // status defines the observed state of Step - // +optional - Status StepStatus `json:"status,omitempty,omitzero"` -} - -// +kubebuilder:object:root=true - -// StepList contains a list of Step -type StepList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Step `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Step{}, &StepList{}) -} From 85a65f81da67826b1fcc5a0fc62267ff8905e674 Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Mon, 24 Nov 2025 14:51:59 +0100 Subject: [PATCH 3/8] adds workload note --- api/v1alpha1/workload.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 api/v1alpha1/workload.go diff --git a/api/v1alpha1/workload.go b/api/v1alpha1/workload.go new file mode 100644 index 000000000..2d0169659 --- /dev/null +++ b/api/v1alpha1/workload.go @@ -0,0 +1,14 @@ +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// TODO: Reflect everything related to the workload (VM, Pod, Volume, etc.) being handled. +// Not sure that makes sense in the initial throw. + +type Workload struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WorkloadSpec `json:"spec,omitempty"` + Status WorkloadStatus `json:"status,omitempty"` +} From 1ff6e2518f5d2c66a19d7195116664eb69ca80d1 Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Mon, 22 Dec 2025 22:50:07 +0100 Subject: [PATCH 4/8] additional thoughts --- api/v1alpha1/README.md | 54 ++++++++++-- api/v1alpha1/decision_types.go | 11 ++- api/v1alpha1/knowledge_types.go | 148 ++++++-------------------------- api/v1alpha1/kpi_types.go | 2 + api/v1alpha1/pipeline_types.go | 6 +- 5 files changed, 84 insertions(+), 137 deletions(-) diff --git a/api/v1alpha1/README.md b/api/v1alpha1/README.md index df9034448..748852f3f 100644 --- a/api/v1alpha1/README.md +++ b/api/v1alpha1/README.md @@ -1,23 +1,51 @@ +This document specifies Cortex CRDs. + +## Pipeline + ```yaml apiVersion: cortex.cloud/v1alpha1 kind: Pipeline metadata: name: nova-general-purpose spec: + description: The primary objective of this pipeline is to load-balance general purpose workloads. # Ordered filters must be completed successfully in order to ensure valid placement and scheduling. + # Failures stop both placement and scheduling and raise a critical alert. filters: - - name: - description: + - name: filter1 + description: Short, human-readable description. + # Optional parameters to configure the filter. params: - a: b + key: value # Ordered weighers should be completed successfully in order to ensure optimal placement and scheduling. + # Failures raise a warning-level alert. weighers: - - name: - description: + - name: weigher1 + description: Short, human-readable description. + # Optional parameters to configure the weigher. params: - c: d + key: value +status: + conditions: + - type: Ready + status: True|False|Unknown + reason: AllFiltersAndWeighersReady + message: All filters and weighers are ready + lastTransitionTime: "" ``` +## Knowledge + +```yaml +apiVersion: cortex.cloud/v1alpha1 +kind: Knowledge +metadata: + name: +spec: {} +status: {} +``` + +## Reservation ```yaml apiVersion: cortex.cloud/v1alpha1 @@ -38,7 +66,15 @@ spec: selector: isNUMAAlignedHost: "true" - - nova: - // everything nova we can't generalize goes here + + # everything nova we can't generalize goes here + nova: {} + +status: + conditions: + - type: Ready + status: True|False|Unknown + reason: ReservationReady + message: Reservation is successfully scheduled + lastTransitionTime: "" ``` diff --git a/api/v1alpha1/decision_types.go b/api/v1alpha1/decision_types.go index 3c85ea105..e84f0b875 100644 --- a/api/v1alpha1/decision_types.go +++ b/api/v1alpha1/decision_types.go @@ -26,7 +26,7 @@ type DecisionSpec struct { // The domain of the decision, indicating what has initiated this decision. Domain SchedulingDomain `json:"type"` - // TODO: Avoid using RawExtension! + // TODO: Avoid using RawExtension. CRDs should be strongly typed. // If the type is "nova", this field contains the raw nova decision request. // +kubebuilder:validation:Optional NovaRaw *runtime.RawExtension `json:"novaRaw,omitempty"` @@ -41,9 +41,11 @@ type DecisionSpec struct { MachineRef *corev1.ObjectReference `json:"machineRef,omitempty"` } +// TODO: Cortex concepts relies on pipelines consisting of filters and weighers, not steps. type StepResult struct { // object reference to the scheduler step. StepRef corev1.ObjectReference `json:"stepRef"` + // TODO: This will likely explode the CRD size limit as each step would hold all hosts and their values. // Activations of the step for each host. Activations map[string]float64 `json:"activations"` } @@ -71,11 +73,14 @@ type DecisionResult struct { } const ( + // TODO: The canonical way is to define phrase this positivly and expose a Ready condition. + // TODO: Comments must start with the item name, e.g. DecisionConditionError ... // Something went wrong during the calculation of the decision. DecisionConditionError = "Error" ) type DecisionStatus struct { + // TODO: This should be exposed as a Prometheus metric, such as a histogram, instead to make it meaningful and gain insight over time. // The time it took to schedule. // +kubebuilder:validation:Optional Took metav1.Duration `json:"took"` @@ -121,7 +126,7 @@ type Decision struct { // metadata is a standard object metadata // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + metav1.ObjectMeta `json:"metadata,omitempty"` // spec defines the desired state of Decision // +required @@ -129,7 +134,7 @@ type Decision struct { // status defines the observed state of Decision // +optional - Status DecisionStatus `json:"status,omitempty,omitzero"` + Status DecisionStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/knowledge_types.go b/api/v1alpha1/knowledge_types.go index f08499d2c..321c2d5ea 100644 --- a/api/v1alpha1/knowledge_types.go +++ b/api/v1alpha1/knowledge_types.go @@ -4,149 +4,51 @@ package v1alpha1 import ( - "encoding/json" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" ) -// Dependencies required for extracting the knowledge. -type KnowledgeDependenciesSpec struct { - // Datasources required for extracting this knowledge. - // If provided, all datasources must have the same database secret reference - // so the knowledge can be joined across multiple database tables. - // +kubebuilder:validation:Optional - Datasources []corev1.ObjectReference `json:"datasources,omitempty"` - - // Other knowledges this knowledge depends on. - // +kubebuilder:validation:Optional - Knowledges []corev1.ObjectReference `json:"knowledges,omitempty"` +type KnowledgeSpec struct { } -type KnowledgeExtractorSpec struct { - // The name of the extractor. - Name string `json:"name,omitempty"` - - // Additional configuration for the extractor. +type KnowledgeStatus struct { + // Conditions reflects current status conditions of the knowledge. // +kubebuilder:validation:Optional - Config runtime.RawExtension `json:"config"` + Conditions []KnowledgeCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } -type KnowledgeSpec struct { - // The operator by which this knowledge should be extracted. - Operator string `json:"operator,omitempty"` - - // The feature extractor to use for extracting this knowledge. - Extractor KnowledgeExtractorSpec `json:"extractor,omitempty"` +// KnowledgeCondition ... +type KnowledgeCondition struct { + // Type of the condition. + Type KnowledgeConditionType `json:"type"` - // The desired recency of this knowledge, i.e. how old it can be until - // it needs to be re-extracted. - // +kubebuilder:default="60s" - Recency metav1.Duration `json:"recency"` + // Status of the condition. + Status metav1.ConditionStatus `json:"status"` - // A human-readable description of the knowledge to be extracted. - // +kubebuilder:validation:Optional - Description string `json:"description,omitempty"` + // LastTransitionTime is the timestamp corresponding to the last status change of this condition. + // +optional + LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - // Dependencies required for extracting this knowledge. - // +kubebuilder:validation:Optional - Dependencies KnowledgeDependenciesSpec `json:"dependencies"` - - // Database credentials for the database where the knowledge will be stored. - // - // Note: this is a legacy feature to stay compatible with the cortex scheduler. - // Once the scheduler is moved to use the knowledge via CRs only, we can - // remove this. - // - // The secret should contain the following keys: - // - "username": The database username. - // - "password": The database password. - // - "host": The database host. - // - "port": The database port. - // - "database": The database name. - DatabaseSecretRef *corev1.SecretReference `json:"databaseSecretRef"` - - // Whether the knowledge should only be stored in the database and not - // in the CR status. - // - // Note: this is a legacy feature. Features should always contain condensed - // knowledge in the CR status for easy access. - // +kubebuilder:default=false - StoreInDatabaseOnly bool `json:"storeInDatabaseOnly,omitempty"` -} + // Reason is a brief machine-readable explanation for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` -// Convert raw features to a list of strongly typed feature structs. -func UnboxFeatureList[T any](raw runtime.RawExtension) ([]T, error) { - var t []T - if len(raw.Raw) == 0 { - return t, nil - } - var rawSerialized struct { - Features []T `json:"features"` - } - if err := json.Unmarshal(raw.Raw, &rawSerialized); err != nil { - return t, err - } - return rawSerialized.Features, nil + // Message is a human-readable description of the details of the last transition. + // +optional + Message string `json:"message,omitempty"` } -// Convert a list of strongly typed feature structs to raw features. -func BoxFeatureList[T any](features []T) (runtime.RawExtension, error) { - raw := runtime.RawExtension{} - var err error - rawSerialized := struct { - Features []T `json:"features"` - }{ - Features: features, - } - raw.Raw, err = json.Marshal(rawSerialized) - return raw, err -} +// KnowledgeConditionType represents a Knowledge condition value. +type KnowledgeConditionType string const ( - // Something went wrong during the extraction of the knowledge. - KnowledgeConditionError = "Error" + // KnowledgeConditionTypeReady indicates that a Knowledge is ready. + KnowledgeConditionTypeReady KnowledgeConditionType = "Ready" ) -type KnowledgeStatus struct { - // When the knowledge was last successfully extracted. - // +kubebuilder:validation:Optional - LastExtracted metav1.Time `json:"lastExtracted"` - // The time it took to perform the last extraction. - // +kubebuilder:validation:Optional - Took metav1.Duration `json:"took"` - - // The raw data behind the extracted knowledge, e.g. a list of features. - // +kubebuilder:validation:Optional - Raw runtime.RawExtension `json:"raw"` - // The number of features extracted, or 1 if the knowledge is not a list. - // +kubebuilder:validation:Optional - RawLength int `json:"rawLength,omitempty"` - - // The current status conditions of the knowledge. - // +kubebuilder:validation:Optional - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` -} - -// Helper function to check if the knowledge is ready. -func (s *KnowledgeStatus) IsReady() bool { - if meta.IsStatusConditionTrue(s.Conditions, KnowledgeConditionError) { - return false - } - return s.RawLength > 0 -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster -// +kubebuilder:printcolumn:name="Operator",type="string",JSONPath=".spec.operator" // +kubebuilder:printcolumn:name="Created",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:printcolumn:name="Extracted",type="date",JSONPath=".status.lastExtracted" -// +kubebuilder:printcolumn:name="Took",type="string",JSONPath=".status.took" -// +kubebuilder:printcolumn:name="Recency",type="string",JSONPath=".spec.recency" -// +kubebuilder:printcolumn:name="Features",type="integer",JSONPath=".status.rawLength" // Knowledge is the Schema for the knowledges API type Knowledge struct { @@ -154,7 +56,7 @@ type Knowledge struct { // metadata is a standard object metadata // +optional - metav1.ObjectMeta `json:"metadata,omitempty,omitzero"` + metav1.ObjectMeta `json:"metadata,omitempty"` // spec defines the desired state of Knowledge // +required @@ -162,7 +64,7 @@ type Knowledge struct { // status defines the observed state of Knowledge // +optional - Status KnowledgeStatus `json:"status,omitempty,omitzero"` + Status KnowledgeStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/kpi_types.go b/api/v1alpha1/kpi_types.go index d1f6723a4..e4b1db92c 100644 --- a/api/v1alpha1/kpi_types.go +++ b/api/v1alpha1/kpi_types.go @@ -1,6 +1,8 @@ // Copyright 2025 SAP SE // SPDX-License-Identifier: Apache-2.0 +// TODO: This needs to reflect the refactoring. + package v1alpha1 import ( diff --git a/api/v1alpha1/pipeline_types.go b/api/v1alpha1/pipeline_types.go index 6b2a48ef2..5822b216f 100644 --- a/api/v1alpha1/pipeline_types.go +++ b/api/v1alpha1/pipeline_types.go @@ -87,8 +87,12 @@ type PipelineCondition struct { } type PipelineStatus struct { + // Filters ... + // +optional Filters []FilterStatus `json:"filters,omitempty"` + // Weighers ... + // +optional Weighers []WeigherStatus `json:"weighers,omitempty"` // Conditions describe the status of the pipeline. @@ -100,10 +104,8 @@ type PipelineStatus struct { // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster // +kubebuilder:printcolumn:name="Created",type="date",JSONPath=".metadata.creationTimestamp" -// +kubebuilder:printcolumn:name="Operator",type="string",JSONPath=".spec.operator" // +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type" // +kubebuilder:printcolumn:name="Ready",type="boolean",JSONPath=".status.ready" -// +kubebuilder:printcolumn:name="Steps",type="string",JSONPath=".status.stepsReadyFrac" // Pipeline is the Schema for the decisions API type Pipeline struct { From f816359ed5b646b14a38ae87533e1ecb6efef3cd Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Mon, 22 Dec 2025 22:53:39 +0100 Subject: [PATCH 5/8] additional note --- api/v1alpha1/kpi_types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/v1alpha1/kpi_types.go b/api/v1alpha1/kpi_types.go index e4b1db92c..8de429995 100644 --- a/api/v1alpha1/kpi_types.go +++ b/api/v1alpha1/kpi_types.go @@ -49,6 +49,7 @@ const ( ) type KPIStatus struct { + // TODO: Either drop the conditions or use a Ready condition. // If the kpi is ready to be executed. Ready bool `json:"ready"` From 1eb5824c76c4b4b9d47da81526e4a8617c4c46cd Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Tue, 6 Jan 2026 09:50:06 +0100 Subject: [PATCH 6/8] Update README with clarification on parameters Clarified optional parameters for filters and weighers with a note on early iteration. --- api/v1alpha1/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/README.md b/api/v1alpha1/README.md index 748852f3f..05b59fc10 100644 --- a/api/v1alpha1/README.md +++ b/api/v1alpha1/README.md @@ -14,7 +14,7 @@ spec: filters: - name: filter1 description: Short, human-readable description. - # Optional parameters to configure the filter. + # Optional parameters to configure the filter. Early iteration to be refined later. params: key: value # Ordered weighers should be completed successfully in order to ensure optimal placement and scheduling. @@ -22,7 +22,7 @@ spec: weighers: - name: weigher1 description: Short, human-readable description. - # Optional parameters to configure the weigher. + # Optional parameters to configure the weigher. Early iteration to be refined later. params: key: value status: From cd1d5dc7bfa3105baad9910e939cf218e54899e6 Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Tue, 6 Jan 2026 14:24:25 +0100 Subject: [PATCH 7/8] knowledge type --- api/v1alpha1/README.md | 33 ++++++++++++++++++++++- api/v1alpha1/knowledge_types.go | 46 ++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/README.md b/api/v1alpha1/README.md index 05b59fc10..4795ed90d 100644 --- a/api/v1alpha1/README.md +++ b/api/v1alpha1/README.md @@ -41,8 +41,39 @@ apiVersion: cortex.cloud/v1alpha1 kind: Knowledge metadata: name: + labels: + domain: nova + entityKind: VM + projectID: "9a2e2b7b-1b5a-4d8f-a0c4-2a9f7e3e1c02" spec: {} -status: {} +status: + entity: + kind: VM + id: "6f2c2b5d-4a5b-4d0d-9b33-0f2d7c2b9c11" + domain: nova + + samples: + cpuUtilization: + value: + quantity: "730m" + observedAt: "2026-01-06T10:10:00Z" + period: 5m + + migrationCostClass: + value: + string: high + observedAt: "2026-01-06T09:00:00Z" + period: 60m + + conditions: + - type: Ready + status: "True" + reason: SamplesFresh + message: All samples within staleness budget + lastTransitionTime: "2026-01-05T10:10:05Z" + + observedGeneration: 1 + lastUpdateTime: "2026-01-05T10:10:05Z" ``` ## Reservation diff --git a/api/v1alpha1/knowledge_types.go b/api/v1alpha1/knowledge_types.go index 321c2d5ea..b6f35339e 100644 --- a/api/v1alpha1/knowledge_types.go +++ b/api/v1alpha1/knowledge_types.go @@ -4,13 +4,57 @@ package v1alpha1 import ( + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type KnowledgeSpec struct { +type KnowledgeSpec struct{} + +// KnowledgeEntityKind identifies the entity type. +type KnowledgeEntityKind string + +const ( + KnowledgeEntityKindVM KnowledgeEntityKind = "VM" + KnowledgeEntityKindHost KnowledgeEntityKind = "Host" + KnowledgeEntityKindProject KnowledgeEntityKind = "Project" +) + +type Entity struct { + // +kubebuilder:validation:Enum=VM;Host;Project + Kind KnowledgeEntityKind `json:"kind"` + ID string `json:"id"` + Domain string `json:"domain"` +} + +// KnowledgeValue is a typed union for discrete sample values. +type KnowledgeValue struct { + // +optional + Quantity *resource.Quantity `json:"quantity,omitempty"` + // +optional + Bool *bool `json:"bool,omitempty"` + // +optional + String *string `json:"string,omitempty"` +} + +// KnowledgeSampleStatus represents the latest discrete sample of a feature. +type KnowledgeSampleStatus struct { + Value KnowledgeValue `json:"value"` + ObservedAt metav1.Time `json:"observedAt"` + // +optional + Period *metav1.Duration `json:"period,omitempty"` } type KnowledgeStatus struct { + // Entity ... + Entity Entity `json:"entity"` + + // Samples ... + // +optional + Samples map[string]KnowledgeSampleStatus `json:"samples,omitempty"` + + // +optional + LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"` + // Conditions reflects current status conditions of the knowledge. // +kubebuilder:validation:Optional Conditions []KnowledgeCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` From 4420bea8003b25702700851c2c8acbe9c22c106e Mon Sep 17 00:00:00 2001 From: Arno Uhlig Date: Tue, 6 Jan 2026 14:31:26 +0100 Subject: [PATCH 8/8] add comments --- api/v1alpha1/README.md | 5 +++++ api/v1alpha1/knowledge_types.go | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/v1alpha1/README.md b/api/v1alpha1/README.md index 4795ed90d..93fab4a6f 100644 --- a/api/v1alpha1/README.md +++ b/api/v1alpha1/README.md @@ -2,6 +2,11 @@ This document specifies Cortex CRDs. ## Pipeline +This example pipeline should be everything needed to configure Cortex. + +Weighers, Filters are well-documented. +Basic requirements, such as the Prometheus URL, are flags and immutable during runtime. + ```yaml apiVersion: cortex.cloud/v1alpha1 kind: Pipeline diff --git a/api/v1alpha1/knowledge_types.go b/api/v1alpha1/knowledge_types.go index b6f35339e..eaf8473cf 100644 --- a/api/v1alpha1/knowledge_types.go +++ b/api/v1alpha1/knowledge_types.go @@ -14,13 +14,13 @@ type KnowledgeSpec struct{} type KnowledgeEntityKind string const ( - KnowledgeEntityKindVM KnowledgeEntityKind = "VM" - KnowledgeEntityKindHost KnowledgeEntityKind = "Host" - KnowledgeEntityKindProject KnowledgeEntityKind = "Project" + KnowledgeEntityKindVM KnowledgeEntityKind = "vm" + KnowledgeEntityKindHost KnowledgeEntityKind = "host" + KnowledgeEntityKindProject KnowledgeEntityKind = "project" ) type Entity struct { - // +kubebuilder:validation:Enum=VM;Host;Project + // +kubebuilder:validation:Enum=vm;host;project Kind KnowledgeEntityKind `json:"kind"` ID string `json:"id"` Domain string `json:"domain"`