commitments

package
v0.0.0-...-9f115a5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: Apache-2.0 Imports: 50 Imported by: 0

Documentation

Index

Constants

View Source
const (

	// Resource type suffixes
	ResourceSuffixRAM       = "_ram"
	ResourceSuffixCores     = "_cores"
	ResourceSuffixInstances = "_instances"
)

Limes LIQUID resource naming convention: hw_version_<flavorgroup>_<resourcetype> Supported resource types: _ram, _cores, _instances

View Source
const (
	SkipReasonUnitMismatch       = "unit_mismatch"
	SkipReasonUnknownFlavorGroup = "unknown_flavor_group"
	SkipReasonInvalidResource    = "invalid_resource_name"
	SkipReasonEmptyUUID          = "empty_uuid"
	SkipReasonNonCompute         = "non_compute"
)

Skip reason labels for commitment processing

Variables

View Source
var (
	// CreatorValue identifies reservations created by this syncer.
	CreatorValue = "commitments-syncer"
)

Functions

func CheckCommitmentsDryRun

func CheckCommitmentsDryRun(ctx context.Context, config E2EChecksConfig)

CheckCommitmentsDryRun verifies that the dry-run path of change-commitments works end-to-end for each (AZ, HandlesCommitments resource) pair. A dry-run request is sent with amount=2; both accepted and capacity-rejected responses are valid outcomes. No cleanup is needed because dry-run does not persist any state.

func CheckCommitmentsInfoEndpoint

func CheckCommitmentsInfoEndpoint(ctx context.Context, config E2EChecksConfig)

CheckCommitmentsInfoEndpoint verifies that GET /commitments/v1/info returns 200 with a valid ServiceInfo.

func CheckCommitmentsMultiFlavorGroupBatch

func CheckCommitmentsMultiFlavorGroupBatch(ctx context.Context, config E2EChecksConfig)

CheckCommitmentsMultiFlavorGroupBatch exercises the pending→batch-confirm flow for each (AZ, resource) pair. For each pair it:

  1. Creates a pending commitment (UUID:A, amount=1, ExpiresAt=10min) — non-confirming, always accepted
  2. Sends an atomic batch: UUID:A pending→confirmed amount=3, UUID:B new confirmed amount=1
  3. If the batch is rejected (no capacity): cleans up the pending UUID:A and continues
  4. If the batch is accepted: parses and logs the usage report; deletes unless NoCleanup=true

This exercises the all-or-nothing semantics of change-commitments: if capacity for the full batch (4 units) is unavailable, neither UUID:A nor UUID:B is confirmed.

func CheckCommitmentsRoundTrip

func CheckCommitmentsRoundTrip(ctx context.Context, config E2EChecksConfig)

CheckCommitmentsRoundTrip iterates all HandlesCommitments resources from /info and for each (AZ, resource) pair:

  1. Creates a confirmed test commitment (amount=2, expires in 5 minutes)
  2. If accepted: calls the usage API to verify it returns a valid response, then deletes the commitment
  3. If rejected: logs the reason and continues — capacity rejection is not an error

Panics on infrastructure failures (non-200 from the API, deletion failure after acceptance).

func DeleteChildReservations

func DeleteChildReservations(ctx context.Context, k8sClient client.Client, cr *v1alpha1.CommittedResource) error

DeleteChildReservations deletes all Reservation CRDs belonging to cr, matched by CommitmentUUID. Called both by the controller on inactive/rollback transitions and by the API handler on CR deletion.

func GetFlavorGroupAndTypeFromResource

func GetFlavorGroupAndTypeFromResource(resourceName string) (string, v1alpha1.CommittedResourceType, error)

GetFlavorGroupAndTypeFromResource extracts the flavor group name and resource type from a LIQUID resource name. Accepts _ram (memory) and _cores (CPU) suffixes. _instances resources are not supported for commitments.

func GetFlavorGroupNameFromResource

func GetFlavorGroupNameFromResource(resourceName string) (string, error)

GetFlavorGroupNameFromResource extracts the flavor group name from a LIQUID resource name. Only accepts _ram resources since CommitmentState is RAM-based. Callers handling _cores or _instances must use a different approach.

func GetMaxSlotIndex

func GetMaxSlotIndex(reservations []v1alpha1.Reservation) int

func GetNextSlotIndex

func GetNextSlotIndex(reservations []v1alpha1.Reservation) int

Always continue counting slots from max, instead of filling gaps.

func LogFlavorGroupResourceConfig

func LogFlavorGroupResourceConfig(log logr.Logger, cfg map[string]FlavorGroupResourcesConfig)

LogFlavorGroupResourceConfig logs the effective RAM unit for each configured flavor group. It warns when RAMUnitGiB is 0, which silently defaults to 1 GiB.

func LoggerFromContext

func LoggerFromContext(ctx context.Context) logr.Logger

LoggerFromContext returns a logger with greq and req values from the context. This creates a child logger with the request tracking values pre-attached, so you don't need to repeat them in every log call.

func ResourceNameCores

func ResourceNameCores(flavorGroup string) string

ResourceNameCores creates a LIQUID resource name for CPU cores from a flavor group name. Format: hw_version_<flavorgroup>_cores

func ResourceNameInstances

func ResourceNameInstances(flavorGroup string) string

ResourceNameInstances creates a LIQUID resource name for instance count from a flavor group name. Format: hw_version_<flavorgroup>_instances

func ResourceNameRAM

func ResourceNameRAM(flavorGroup string) string

ResourceNameRAM creates a LIQUID resource name for RAM from a flavor group name. Format: hw_version_<flavorgroup>_ram

func RunCommitmentsE2EChecks

func RunCommitmentsE2EChecks(ctx context.Context, config E2EChecksConfig)

RunCommitmentsE2EChecks runs all e2e checks for the commitments API.

func ScanAZForPaygCandidates

func ScanAZForPaygCandidates(
	ctx context.Context,
	k8sClient client.Client,
	vmSource reservations.VMSource,
	az string,
	projectID string,
	flavorGroup compute.FlavorGroupFeature,
) (map[string][]PAYGCandidate, error)

ScanAZForPaygCandidates returns unallocated PAYG VMs across all HVs in az, grouped by HV name. Makes exactly two calls regardless of HV or VM count:

  1. List all CR Reservations → build allocated VM UUID set from Spec.Allocations
  2. VMSource.ListVMsByProject → enriched VM data for the project

HVs are identified by the label "topology.kubernetes.io/zone".

func WithGlobalRequestID

func WithGlobalRequestID(ctx context.Context, greq string) context.Context

WithGlobalRequestID creates a new context with the specified global request ID. This is used to propagate existing request IDs (e.g., from the creator annotation).

func WithNewGlobalRequestID

func WithNewGlobalRequestID(ctx context.Context) context.Context

WithNewGlobalRequestID creates a new context with a committed-resource-prefixed global request ID.

Types

type APIConfig

type APIConfig struct {
	// EnableChangeCommitments controls whether the change-commitments endpoint is active.
	// When false the endpoint returns HTTP 503; the info endpoint remains available.
	EnableChangeCommitments bool `json:"enableChangeCommitments"`
	// EnableReportUsage controls whether the report-usage endpoint is active.
	EnableReportUsage bool `json:"enableReportUsage"`
	// EnableReportCapacity controls whether the report-capacity endpoint is active.
	EnableReportCapacity bool `json:"enableReportCapacity"`
	// EnableQuotaAPI controls whether the quota API endpoint is active.
	// When false, the endpoint will return HTTP 503 Service Unavailable.
	EnableQuotaAPI bool `json:"enableQuota"`
	// WatchTimeout is how long the change-commitments handler polls CommittedResource
	// CRD conditions before giving up and rolling back.
	WatchTimeout metav1.Duration `json:"watchTimeout"`
	// WatchPollInterval is how frequently the change-commitments handler polls
	// CommittedResource CRD conditions while waiting for the controller outcome.
	WatchPollInterval metav1.Duration `json:"watchPollInterval"`
	// FlavorGroupResourceConfig maps flavor group IDs to resource flag configs; "*" acts as catch-all.
	FlavorGroupResourceConfig map[string]FlavorGroupResourcesConfig `json:"flavorGroupResourceConfig,omitempty"`
	// QuotaServedAvailabilityZones restricts quota handling to these AZs.
	// Quota received for AZs not in this list is silently skipped.
	// If empty/nil, no pre-filtering is applied (relies on error-based fallback).
	QuotaServedAvailabilityZones []string `json:"quotaServedAvailabilityZones,omitempty"`
}

APIConfig holds configuration for the LIQUID commitment HTTP endpoints.

func DefaultAPIConfig

func DefaultAPIConfig() APIConfig

func (APIConfig) ResourceConfigForGroup

func (c APIConfig) ResourceConfigForGroup(groupName string) FlavorGroupResourcesConfig

ResourceConfigForGroup returns the resource config for the given flavor group name, falling back to the "*" catch-all if no exact match exists.

type ApplyResult

type ApplyResult struct {
	// Created is the number of reservations created
	Created int
	// Deleted is the number of reservations deleted
	Deleted int
	// Repaired is the number of reservations repaired (metadata sync or recreated due to wrong config)
	Repaired int
	// TotalSlots is the total number of reservation slots that should exist after the apply.
	// Used by the CR controller to wait for the correct number of children in the cache.
	TotalSlots int
	// TouchedReservations are reservations that were created or updated
	TouchedReservations []v1alpha1.Reservation
	// RemovedReservations are reservations that were deleted
	RemovedReservations []v1alpha1.Reservation
}

ApplyResult contains the result of applying a commitment state.

type CRControllerMonitor

type CRControllerMonitor struct {
	// contains filtered or unexported fields
}

CRControllerMonitor reports the number of CommittedResource CRDs that are actively awaiting reservation placement (Reason=Reserving, AllowRejection=false).

This includes both CRs on their first placement attempt and those being retried after a failure. API-originated dry-run probes (AllowRejection=true) are excluded. The metric is absent for a given label set when no CRs match — absence means zero.

func NewCRControllerMonitor

func NewCRControllerMonitor(c client.Client) CRControllerMonitor

func (*CRControllerMonitor) Collect

func (m *CRControllerMonitor) Collect(ch chan<- prometheus.Metric)

Collect implements prometheus.Collector. Lists all CommittedResource CRDs and counts those with Ready=False/Reason=Reserving and AllowRejection=false, grouped by flavor group, resource type, and AZ.

func (*CRControllerMonitor) Describe

func (m *CRControllerMonitor) Describe(ch chan<- *prometheus.Desc)

Describe implements prometheus.Collector.

func (*CRControllerMonitor) RecordSlotLimitRejection

func (m *CRControllerMonitor) RecordSlotLimitRejection(flavorGroup, az string)

RecordSlotLimitRejection increments the slot-limit rejection counter for the given flavor group and AZ.

type CapacityCalculator

type CapacityCalculator struct {
	// contains filtered or unexported fields
}

CapacityCalculator computes capacity reports for Limes LIQUID API.

func NewCapacityCalculator

func NewCapacityCalculator(client client.Client, conf APIConfig) *CapacityCalculator

func (*CapacityCalculator) CalculateCapacity

CalculateCapacity computes per-AZ capacity for all flavor groups. For each flavor group, three resources are reported: _ram, _cores, _instances. All values are read from FlavorGroupCapacity CRDs pre-computed by the capacity controller:

  • Capacity: RunningInstances + ExclusivelyFreeCapacity converted to slots.
  • Usage: RunningInstances / RunningResources.

type Commitment

type Commitment struct {
	// A unique numerical identifier for this commitment. This API uses this
	// numerical ID to refer to the commitment in other API calls.
	ID int `json:"id"`
	// A unique string identifier for this commitment. The next major version of
	// this API will use this UUID instead of the numerical ID to refer to
	// commitments in API calls.
	UUID string `json:"uuid"`
	// The resource for which usage is committed.
	ServiceType  string `json:"service_type"`
	ResourceName string `json:"resource_name"`
	// The availability zone in which usage is committed.
	AvailabilityZone string `json:"availability_zone"`
	// The amount of usage that was committed to.
	Amount uint64 `json:"amount"`
	// For measured resources, the unit for this resource. The value from the
	// amount field is measured in this unit.
	Unit string `json:"unit"`
	// The requested duration of this commitment, expressed as a comma-separated
	// sequence of positive integer multiples of time units like "1 year,
	// 3 months". Acceptable time units include "second", "minute", "hour",
	// "day", "month" and "year".
	Duration string `json:"duration"`
	// UNIX timestamp when this commitment was created.
	CreatedAt uint64 `json:"created_at"`
	// UNIX timestamp when this commitment should be confirmed. Only shown if
	// this was given when creating the commitment, to delay confirmation into
	// the future.
	ConfirmBy *uint64 `json:"confirm_by,omitempty"`
	// UNIX timestamp when this commitment was confirmed. Only shown after
	// confirmation.
	ConfirmedAt *uint64 `json:"confirmed_at,omitempty"`
	// UNIX timestamp when this commitment is set to expire. Note that the
	// duration counts from confirmBy (or from createdAt for immediately-
	// confirmed commitments) and is calculated at creation time, so this is
	// also shown on unconfirmed commitments.
	ExpiresAt uint64 `json:"expires_at"`
	// Whether the commitment is marked for transfer to a different project.
	// Transferable commitments do not count towards quota calculation in their
	// project, but still block capacity and still count towards billing. Not
	// shown if false.
	Transferable bool `json:"transferable"`
	// The current status of this commitment. If provided, one of "planned",
	// "pending", "guaranteed", "confirmed", "superseded", or "expired".
	Status string `json:"status,omitempty"`
	// Whether a mail notification should be sent if a created commitment is
	// confirmed. Can only be set if the commitment contains a confirmBy value.
	NotifyOnConfirm bool `json:"notify_on_confirm"`

	// The openstack project ID this commitment is for.
	ProjectID string `json:"project_id"`
	// The openstack domain ID this commitment is for.
	DomainID string `json:"domain_id"`
}

Commitment model from the limes API. See: https://github.com/sapcc/limes/blob/5ea068b/docs/users/api-spec-resources.md?plain=1#L493 See: https://github.com/sapcc/go-api-declarations/blob/94ee3e5/limes/resources/commitment.go#L19

type CommitmentReservationController

type CommitmentReservationController struct {
	// Client for the kubernetes API.
	client.Client
	// Kubernetes scheme to use for the reservations.
	Scheme *runtime.Scheme
	// Configuration for the controller.
	Conf ReservationControllerConfig
	// SchedulerClient for making scheduler API calls.
	SchedulerClient *reservations.SchedulerClient
	// DomainResolver resolves OpenStack domain IDs to domain names so the
	// domain_name scheduler hint can be populated for filter_external_customer.
	// Nil when KeystoneSecretRef is not configured; hint is omitted in that case.
	DomainResolver DomainResolver
}

CommitmentReservationController reconciles commitment Reservation objects

func (*CommitmentReservationController) Init

Init initializes the reconciler with required clients and DB connection.

func (*CommitmentReservationController) Reconcile

Reconcile is part of the main kubernetes reconciliation loop which aims to move the current state of the cluster closer to the desired state. Note: This controller only handles commitment reservations, as filtered by the predicate.

func (*CommitmentReservationController) SetupWithManager

func (r *CommitmentReservationController) SetupWithManager(mgr ctrl.Manager, mcl *multicluster.Client) error

SetupWithManager sets up the controller with the Manager.

type CommitmentState

type CommitmentState struct {
	// CommitmentUUID is the UUID of the commitment this state corresponds to.
	CommitmentUUID string
	// ProjectID is the OpenStack project this commitment belongs to
	ProjectID string
	// DomainID is the OpenStack domain this commitment belongs to
	DomainID string
	// FlavorGroupName identifies the flavor group (e.g., "hana_medium_v2")
	FlavorGroupName string
	// ResourceType is the kind of resource committed: memory or cores.
	ResourceType v1alpha1.CommittedResourceType
	// TotalMemoryBytes is the total memory in bytes across all reservation slots (memory commitments only).
	TotalMemoryBytes int64
	// TotalCores is the number of committed CPU cores (cores commitments only).
	TotalCores int64
	// AvailabilityZone specifies the availability zone for this commitment
	AvailabilityZone string
	// StartTime is when the commitment becomes active
	StartTime *time.Time
	// EndTime is when the commitment expires
	EndTime *time.Time
	// CreatorRequestID is the request ID that triggered this state change (for traceability)
	CreatorRequestID string
	// NamePrefix overrides the default "commitment-<uuid>-" reservation naming convention.
	// When set (e.g. "<cr-name>-"), Reservation CRDs are named "<NamePrefix><slot-index>".
	// Used by the CommittedResource controller; leave empty for the legacy syncer path.
	NamePrefix string
	// ParentGeneration is the Generation of the parent CommittedResource CRD. Written into
	// Reservation spec so the Reservation controller can echo it back in status, letting
	// the CR controller detect when all children have been processed for the current spec.
	// Zero for syncer-created reservations (no parent CR).
	ParentGeneration int64
	// State is the lifecycle state from Limes (planned/pending/guaranteed/confirmed/superseded/expired).
	State v1alpha1.CommitmentStatus
}

CommitmentState represents desired or current commitment resource allocation.

func FromChangeCommitmentTargetState

func FromChangeCommitmentTargetState(
	commitment liquid.Commitment,
	projectID string,
	domainID string,
	flavorGroupName string,
	resourceType v1alpha1.CommittedResourceType,
	az string,
	ramUnitMiB uint64,
) (*CommitmentState, error)

FromChangeCommitmentTargetState converts LIQUID API request to CommitmentState. ramUnitMiB is the size of one external RAM unit in MiB:

  • fixed-ratio flavor groups: SmallestFlavor.MemoryMB (1 unit = 1 smallest-flavor slot)
  • variable-ratio flavor groups: 1024 (1 unit = 1 GiB)

func FromCommitment

func FromCommitment(
	commitment Commitment,
	ramUnitMiB uint64,
) (*CommitmentState, error)

FromCommitment converts Limes commitment to CommitmentState. ramUnitMiB is the size of one external RAM unit in MiB, as returned by RAMResourceTypeConfig.RAMUnitMiB():

  • fixed-ratio flavor groups: SmallestFlavor.MemoryMB (1 unit = 1 smallest-flavor slot)
  • variable-ratio flavor groups: 1024 (1 unit = 1 GiB)

func FromCommittedResource

func FromCommittedResource(cr v1alpha1.CommittedResource) (*CommitmentState, error)

FromCommittedResource reads CommitmentState from a CommittedResource CRD.

func FromReservations

func FromReservations(reservations []v1alpha1.Reservation) (*CommitmentState, error)

FromReservations reconstructs CommitmentState from existing Reservation CRDs.

type CommitmentStateWithUsage

type CommitmentStateWithUsage struct {
	CommitmentState
	RemainingMemoryBytes int64
	AssignedInstances    []string
	UsedVCPUs            int64
}

CommitmentStateWithUsage extends CommitmentState with usage tracking for billing calculations.

func NewCommitmentStateWithUsage

func NewCommitmentStateWithUsage(state *CommitmentState) *CommitmentStateWithUsage

NewCommitmentStateWithUsage creates a CommitmentStateWithUsage from a CommitmentState.

func (*CommitmentStateWithUsage) AssignVM

func (c *CommitmentStateWithUsage) AssignVM(vmUUID string, vmMemoryBytes, vCPUs int64) bool

AssignVM attempts to assign a VM to this commitment if there's enough capacity.

func (*CommitmentStateWithUsage) HasRemainingCapacity

func (c *CommitmentStateWithUsage) HasRemainingCapacity() bool

HasRemainingCapacity returns true if the commitment has any remaining capacity.

type CommitmentsClient

type CommitmentsClient interface {
	// Init the client.
	Init(ctx context.Context, client client.Client, conf SyncerConfig) error
	// List all projects to resolve commitments.
	ListProjects(ctx context.Context) ([]Project, error)
	// List all commitments with resolved metadata (e.g. project, flavor, ...).
	ListCommitmentsByID(ctx context.Context, projects ...Project) (map[string]Commitment, error)
}

Client to fetch commitments.

func NewCommitmentsClient

func NewCommitmentsClient() CommitmentsClient

type CommittedResourceController

type CommittedResourceController struct {
	client.Client
	Scheme  *runtime.Scheme
	Conf    CommittedResourceControllerConfig
	Monitor *CRControllerMonitor
	// VMSource enables PAYG pre-allocation when creating reservation slots. When nil the
	// PAYG scan is skipped and all slots are created via blind scheduler probes.
	VMSource reservations.VMSource
}

CommittedResourceController reconciles CommittedResource CRDs and owns all child Reservation CRUD.

func (*CommittedResourceController) Reconcile

func (*CommittedResourceController) SetupWithManager

func (r *CommittedResourceController) SetupWithManager(mgr ctrl.Manager, mcl *multicluster.Client) error

SetupWithManager sets up the controller with the Manager.

type CommittedResourceControllerConfig

type CommittedResourceControllerConfig struct {
	// RequeueIntervalRetry is the base back-off interval when placement fails (AllowRejection=false path).
	// The actual delay doubles with each consecutive failure: base * 2^min(failures, 6), capped at MaxRequeueInterval.
	// If zero (unconfigured), backoff is disabled and the controller retries immediately on every failure.
	RequeueIntervalRetry metav1.Duration `json:"requeueIntervalRetry"`

	// MaxRequeueInterval caps the exponential backoff delay.
	// Once this ceiling is reached, every subsequent retry fires after exactly this interval.
	MaxRequeueInterval metav1.Duration `json:"maxRequeueInterval"`

	// SlotCreationDelay is the pause inserted between consecutive Reservation CRD creates.
	// Spreads scheduler calls over time instead of bursting them all at once.
	// 0 disables the delay.
	SlotCreationDelay metav1.Duration `json:"slotCreationDelay"`

	// MaxSlotsPerCommitment caps the number of Reservation CRDs that may be created for a single
	// CommittedResource on the AllowRejection=true (API) path. Requests that would exceed this
	// limit are rejected immediately before any slots are created.
	// Has no effect on the AllowRejection=false (syncer) path.
	// 0 disables the cap.
	MaxSlotsPerCommitment int `json:"maxSlotsPerCommitment"`

	// EnablePaygPreAllocation enables scanning the AZ for existing PAYG VMs before creating
	// blind reservation slots. When true, the controller absorbs matching PAYG VMs into
	// pre-populated slots, consuming CR delta before falling back to the blind scheduler path.
	EnablePaygPreAllocation bool `json:"enablePaygPreAllocation,omitempty"`
}

CommittedResourceControllerConfig holds tuning knobs for the CommittedResource CRD controller.

type Config

type Config struct {
	ReservationController       ReservationControllerConfig       `json:"committedResourceReservationController"`
	CommittedResourceController CommittedResourceControllerConfig `json:"committedResourceController"`
	UsageReconciler             UsageReconcilerConfig             `json:"committedResourceUsageReconciler"`
	API                         APIConfig                         `json:"committedResourceAPI"`

	// DatasourceName is the name of the Datasource CRD that provides database
	// connection info. Used to construct the UsageDBClient for report-usage and usage reconciler.
	DatasourceName string `json:"datasourceName,omitempty"`
}

Config aggregates configuration for all commitments components. Each controller and the API have their own sub-struct so that unrelated fields are never visible to the wrong component.

type DomainResolver

type DomainResolver interface {
	// ResolveDomainName returns the name of the domain with the given ID.
	// Returns an error if the domain cannot be found or the lookup fails.
	ResolveDomainName(ctx context.Context, domainID string) (string, error)
}

DomainResolver resolves OpenStack domain IDs to their human-readable names. Implementations must be safe for concurrent use.

type E2EChecksConfig

type E2EChecksConfig struct {
	// BaseURL for the commitments API. If empty, defaults to defaultCommitmentsAPIURL.
	BaseURL string `json:"baseURL"`
	// NoCleanup prevents test commitments from being deleted after a successful run.
	// Useful for local inspection with Tilt. Zero value (false) means cleanup runs normally.
	NoCleanup bool `json:"noCleanup,omitempty"`
	// ProjectID is the OpenStack project UUID for all e2e test commitments.
	// If empty, falls back to RoundTripCheck.TestProjectID, then defaultE2EProjectUUID.
	ProjectID string `json:"projectID,omitempty"`
	// AZs is the list of availability zones to test. If empty, falls back to
	// RoundTripCheck.AZ, then uses "qa-de-1b".
	AZs []string `json:"azs,omitempty"`
	// RequestTimeout is the per-request HTTP timeout for change-commitments calls.
	// Defaults to 30s if unset.
	RequestTimeout metav1.Duration `json:"requestTimeout,omitempty"`
	// RoundTripCheck holds optional overrides for backward compatibility.
	// Prefer the top-level ProjectID and AZs fields for new configurations.
	RoundTripCheck *E2ERoundTripConfig `json:"roundTripCheck,omitempty"`
}

E2EChecksConfig holds the configuration for CR e2e checks.

type E2ERoundTripConfig deprecated

type E2ERoundTripConfig struct {
	// AZ is the availability zone to use (e.g. "qa-de-1d"). Defaults to "" if not set.
	AZ string `json:"az"`
	// TestProjectID is the OpenStack project UUID to create test commitments under.
	// Defaults to defaultE2EProjectUUID if not set.
	TestProjectID string `json:"testProjectID"`
}

E2ERoundTripConfig holds optional overrides for the create→delete round-trip e2e check.

Deprecated: use E2EChecksConfig.ProjectID and E2EChecksConfig.AZs instead.

type Flavor

type Flavor struct {
	ID          string  `json:"id"`
	Disk        int     `json:"disk"` // in GB.
	RAM         int     `json:"ram"`  // in MB.
	Name        string  `json:"name"`
	RxTxFactor  float64 `json:"rxtx_factor"`
	VCPUs       int     `json:"vcpus"`
	IsPublic    bool    `json:"os-flavor-access:is_public"`
	Ephemeral   int     `json:"OS-FLV-EXT-DATA:ephemeral"`
	Description string  `json:"description"`

	// JSON string of extra specifications used when scheduling the flavor.
	ExtraSpecs map[string]string `json:"extra_specs" db:"extra_specs"`
}

OpenStack flavor model as returned by the Nova API under /flavors/detail. See: https://docs.openstack.org/api-ref/compute/#list-flavors

type FlavorGroupResourcesConfig

type FlavorGroupResourcesConfig struct {
	RAM       RAMResourceTypeConfig `json:"ram"`
	Cores     ResourceTypeConfig    `json:"cores"`
	Instances ResourceTypeConfig    `json:"instances"`
}

FlavorGroupResourcesConfig groups resource type configs for the three resources of a flavor group.

func ResourceConfigForGroup

func ResourceConfigForGroup(cfg map[string]FlavorGroupResourcesConfig, groupName string) FlavorGroupResourcesConfig

ResourceConfigForGroup returns the resource config for the given flavor group name, falling back to the "*" catch-all if no exact match exists.

type PAYGCandidate

type PAYGCandidate struct {
	VMID       string
	HVName     string
	MemoryMB   uint64
	FlavorName string // exact flavor of the VM — used directly as the slot ResourceName
}

PAYGCandidate is an unallocated PAYG VM suitable for pre-allocation into a reservation slot.

type Project

type Project struct {
	// DomainID is the domain ID the project belongs to.
	DomainID string `json:"domain_id"`
	// ID is the unique ID of the project.
	ID string `json:"id"`
	// Name is the name of the project.
	Name string `json:"name"`
	// ParentID is the parent_id of the project.
	ParentID string `json:"parent_id"`
}

OpenStack project model as returned by the Keystone API under /projects. See: https://docs.openstack.org/api-ref/identity/v3/#projects

type RAMResourceTypeConfig

type RAMResourceTypeConfig struct {
	HandlesCommitments bool `json:"handlesCommitments"`
	HandlesDryRun      bool `json:"handlesDryRun"`
	HasCapacity        bool `json:"hasCapacity"`
	HasQuota           bool `json:"hasQuota"`
	// RAMUnitGiB is the size of one declared LIQUID RAM unit in GiB.
	// Fixed-ratio groups set this to the smallest flavor's RAM (e.g. 480 for a 480 GiB HANA slot).
	// Variable-ratio groups set this to 1 (1 unit = 1 GiB).
	// Defaults to 1 if unset.
	RAMUnitGiB uint64 `json:"ramUnitGiB,omitempty"`
}

RAMResourceTypeConfig extends ResourceTypeConfig with RAM-specific unit configuration.

func (RAMResourceTypeConfig) DeclaredUnitsToGiB

func (c RAMResourceTypeConfig) DeclaredUnitsToGiB(units int64) int64

DeclaredUnitsToGiB converts a value in declared LIQUID units to GiB.

func (RAMResourceTypeConfig) GiBToDeclaredUnits

func (c RAMResourceTypeConfig) GiBToDeclaredUnits(gib int64) int64

GiBToDeclaredUnits converts a GiB value to declared LIQUID units.

func (RAMResourceTypeConfig) RAMUnitMiB

func (c RAMResourceTypeConfig) RAMUnitMiB() uint64

RAMUnitMiB returns the RAM unit in MiB (RAMUnitGiB × 1024), defaulting to 1024 if unset.

type ReservationControllerConfig

type ReservationControllerConfig struct {
	// RequeueIntervalActive is how often to re-verify a healthy Reservation CRD.
	RequeueIntervalActive metav1.Duration `json:"requeueIntervalActive"`
	// RequeueIntervalRetry is the back-off interval when knowledge is unavailable.
	RequeueIntervalRetry metav1.Duration `json:"requeueIntervalRetry"`
	// RequeueIntervalGracePeriod is how often to re-check while a VM allocation
	// is still within AllocationGracePeriod. Shorter than RequeueIntervalActive.
	RequeueIntervalGracePeriod metav1.Duration `json:"requeueIntervalGracePeriod"`
	// AllocationGracePeriod is the time window after a VM is allocated to a
	// reservation during which it's expected to appear on the target host.
	// VMs not confirmed within this period are considered stale and removed.
	AllocationGracePeriod metav1.Duration `json:"allocationGracePeriod"`
	// SchedulerURL is the endpoint of the nova external scheduler.
	SchedulerURL string `json:"schedulerURL"`
	// PipelineDefault is the fallback pipeline when no FlavorGroupPipelines entry matches.
	PipelineDefault string `json:"pipelineDefault"`
	// FlavorGroupPipelines maps flavor group IDs to pipeline names; "*" acts as catch-all.
	FlavorGroupPipelines map[string]string `json:"flavorGroupPipelines,omitempty"`
	// KeystoneSecretRef references a Kubernetes Secret that holds OpenStack credentials
	// used to resolve domain IDs to domain names for the domain_name scheduler hint.
	// The secret must contain the same keys as the syncer's keystoneSecretRef.
	// When empty, domain name resolution is skipped and filter_external_customer will
	// not enforce domain restrictions for CR reservations.
	KeystoneSecretRef corev1.SecretReference `json:"keystoneSecretRef,omitempty"`
	// SSOSecretRef is an optional reference to a Secret holding SSO credentials.
	// Required in environments that use SSO-based Keystone authentication.
	// When nil, http.DefaultClient is used, which will fail in SSO-only environments.
	SSOSecretRef *corev1.SecretReference `json:"ssoSecretRef,omitempty"`
}

ReservationControllerConfig holds tuning knobs for the Reservation CRD controller.

type ReservationManager

type ReservationManager struct {
	client.Client
	// contains filtered or unexported fields
}

ReservationManager handles CRUD operations for Reservation CRDs.

func NewReservationManager

func NewReservationManager(k8sClient client.Client, cfg ReservationManagerConfig) *ReservationManager

NewReservationManager creates a ReservationManager using the given client and config.

func (*ReservationManager) ApplyCommitmentState

func (m *ReservationManager) ApplyCommitmentState(
	ctx context.Context,
	log logr.Logger,
	desiredState *CommitmentState,
	flavorGroups map[string]compute.FlavorGroupFeature,
	creator string,
) (*ApplyResult, error)

ApplyCommitmentState synchronizes Reservation CRDs to match the desired commitment state. This function performs CRUD operations (create/update/delete) on reservation slots to align with the capacity specified in desiredState.

Entry points:

  • from Syncer - periodic sync with Limes state
  • from API ChangeCommitmentsHandler - batch processing of commitment changes

The function is idempotent and handles:

  • Repairing inconsistent slots (wrong flavor group/project)
  • Creating new reservation slots when capacity increases
  • Deleting unused/excess slots when capacity decreases
  • Syncing reservation metadata for all remaining slots

Returns ApplyResult containing touched/removed reservations and counts for metrics.

type ReservationManagerConfig

type ReservationManagerConfig struct {
	// SlotCreationDelay adds a pause between consecutive Reservation CRD creates to spread
	// scheduler load across time rather than bursting all creates at once.
	SlotCreationDelay time.Duration
	// MaxSlots caps the number of new blind-scheduler slots created per apply call.
	// PAYG remapping slots and already-existing slots are excluded from this limit.
	// Only set by the caller on the AllowRejection=true (API) path.
	MaxSlots                int
	EnablePaygPreAllocation bool
	VMSource                reservations.VMSource
}

ReservationManagerConfig holds options for ReservationManager.

type ResourceTypeConfig

type ResourceTypeConfig struct {
	HandlesCommitments bool `json:"handlesCommitments"`
	HandlesDryRun      bool `json:"handlesDryRun"`
	HasCapacity        bool `json:"hasCapacity"`
	HasQuota           bool `json:"hasQuota"`
}

ResourceTypeConfig holds per-resource flags for a single resource type within a flavor group.

type Server

type Server struct {
	ID                             string  `json:"id" db:"id,primarykey"`
	Name                           string  `json:"name" db:"name"`
	Status                         string  `json:"status" db:"status"`
	TenantID                       string  `json:"tenant_id" db:"tenant_id"`
	UserID                         string  `json:"user_id" db:"user_id"`
	HostID                         string  `json:"hostId" db:"host_id"`
	Created                        string  `json:"created" db:"created"`
	Updated                        string  `json:"updated" db:"updated"`
	AccessIPv4                     string  `json:"accessIPv4" db:"access_ipv4"`
	AccessIPv6                     string  `json:"accessIPv6" db:"access_ipv6"`
	OSDCFdiskConfig                string  `json:"OS-DCF:diskConfig" db:"os_dcf_disk_config"`
	Progress                       int     `json:"progress" db:"progress"`
	OSEXTAvailabilityZone          string  `json:"OS-EXT-AZ:availability_zone" db:"os_ext_az_availability_zone"`
	ConfigDrive                    string  `json:"config_drive" db:"config_drive"`
	KeyName                        string  `json:"key_name" db:"key_name"`
	OSSRVUSGLaunchedAt             string  `json:"OS-SRV-USG:launched_at" db:"os_srv_usg_launched_at"`
	OSSRVUSGTerminatedAt           *string `json:"OS-SRV-USG:terminated_at" db:"os_srv_usg_terminated_at"`
	OSEXTSRVATTRHost               string  `json:"OS-EXT-SRV-ATTR:host" db:"os_ext_srv_attr_host"`
	OSEXTSRVATTRInstanceName       string  `json:"OS-EXT-SRV-ATTR:instance_name" db:"os_ext_srv_attr_instance_name"`
	OSEXTSRVATTRHypervisorHostname string  `json:"OS-EXT-SRV-ATTR:hypervisor_hostname" db:"os_ext_srv_attr_hypervisor_hostname"`
	OSEXTSTSTaskState              *string `json:"OS-EXT-STS:task_state" db:"os_ext_sts_task_state"`
	OSEXTSTSVmState                string  `json:"OS-EXT-STS:vm_state" db:"os_ext_sts_vm_state"`
	OSEXTSTSPowerState             int     `json:"OS-EXT-STS:power_state" db:"os_ext_sts_power_state"`

	// From nested JSON
	FlavorName string `json:"-" db:"flavor_name"`
}

OpenStack server model as returned by the Nova API under /servers/detail. See: https://docs.openstack.org/api-ref/compute/#list-servers-detailed

func (*Server) MarshalJSON

func (s *Server) MarshalJSON() ([]byte, error)

Custom marshaler for OpenStackServer to handle nested JSON.

func (*Server) UnmarshalJSON

func (s *Server) UnmarshalJSON(data []byte) error

Custom unmarshaler for OpenStackServer to handle nested JSON.

type SlotLimitExceededError

type SlotLimitExceededError struct {
	NewSlots int
	Limit    int
}

SlotLimitExceededError is returned by ApplyCommitmentState when the number of new reservation slots would exceed MaxSlots. It is a distinct type so callers can detect and metric it separately from ordinary capacity failures.

func (*SlotLimitExceededError) Error

func (e *SlotLimitExceededError) Error() string

type Syncer

type Syncer struct {
	// Client to fetch commitments from Limes
	CommitmentsClient
	// Kubernetes client for CRD operations
	client.Client
	// contains filtered or unexported fields
}

func NewSyncer

func NewSyncer(k8sClient client.Client, monitor *SyncerMonitor) *Syncer

func (*Syncer) Init

func (s *Syncer) Init(ctx context.Context, config SyncerConfig) error

func (*Syncer) SyncReservations

func (s *Syncer) SyncReservations(ctx context.Context) error

SyncReservations fetches commitments from Limes and synchronizes Reservation CRDs.

type SyncerConfig

type SyncerConfig struct {
	// Secret ref to keystone credentials stored in a k8s secret.
	KeystoneSecretRef corev1.SecretReference `json:"keystoneSecretRef"`
	// Secret ref to SSO credentials stored in a k8s secret, if applicable.
	SSOSecretRef *corev1.SecretReference `json:"ssoSecretRef"`
	// SyncInterval defines how often the syncer reconciles Limes commitments to Reservation CRDs.
	SyncInterval metav1.Duration `json:"committedResourceSyncInterval"`
	// FlavorGroupResourceConfig maps flavor group names to resource configs; "*" acts as catch-all.
	// Not read from JSON — populated by the caller from the shared APIConfig.
	FlavorGroupResourceConfig map[string]FlavorGroupResourcesConfig
}

func (SyncerConfig) ResourceConfigForGroup

func (c SyncerConfig) ResourceConfigForGroup(groupName string) FlavorGroupResourcesConfig

ResourceConfigForGroup returns the resource config for the given flavor group name.

type SyncerMonitor

type SyncerMonitor struct {
	// contains filtered or unexported fields
}

SyncerMonitor provides metrics for the commitment syncer.

func NewSyncerMonitor

func NewSyncerMonitor() *SyncerMonitor

NewSyncerMonitor creates a new monitor with Prometheus metrics.

func (*SyncerMonitor) Collect

func (m *SyncerMonitor) Collect(ch chan<- prometheus.Metric)

Collect implements prometheus.Collector.

func (*SyncerMonitor) Describe

func (m *SyncerMonitor) Describe(ch chan<- *prometheus.Desc)

Describe implements prometheus.Collector.

func (*SyncerMonitor) RecordCRCreates

func (m *SyncerMonitor) RecordCRCreates(count int)

func (*SyncerMonitor) RecordCRDeletes

func (m *SyncerMonitor) RecordCRDeletes(count int)

func (*SyncerMonitor) RecordCRUpdates

func (m *SyncerMonitor) RecordCRUpdates(count int)

func (*SyncerMonitor) RecordCommitmentSkipped

func (m *SyncerMonitor) RecordCommitmentSkipped(reason string)

func (*SyncerMonitor) RecordDuration

func (m *SyncerMonitor) RecordDuration(seconds float64)

func (*SyncerMonitor) RecordError

func (m *SyncerMonitor) RecordError()

func (*SyncerMonitor) RecordStaleCRs

func (m *SyncerMonitor) RecordStaleCRs(count int)

func (*SyncerMonitor) SetLimesCommitmentsActive

func (m *SyncerMonitor) SetLimesCommitmentsActive(count int)

type UsageCalculator

type UsageCalculator struct {
	// contains filtered or unexported fields
}

UsageCalculator computes usage reports for Limes LIQUID API.

func NewUsageCalculator

func NewUsageCalculator(client client.Client, vmSource reservations.VMSource, config APIConfig) *UsageCalculator

NewUsageCalculator creates a new UsageCalculator instance.

func (*UsageCalculator) BuildVMAssignmentsFromStatus

func (c *UsageCalculator) BuildVMAssignmentsFromStatus(ctx context.Context, projectID string) (map[string]string, error)

BuildVMAssignmentsFromStatus reads pre-computed VM-to-commitment assignments from CommittedResource CRD status. Returns a map of vmUUID → commitmentUUID (empty string = PAYG). This is the read path that replaces the inline assignment algorithm in the usage API.

func (*UsageCalculator) CalculateUsage

func (c *UsageCalculator) CalculateUsage(
	ctx context.Context,
	log logr.Logger,
	projectID string,
	allAZs []liquid.AvailabilityZone,
) (liquid.ServiceUsageReport, error)

CalculateUsage computes the usage report for a specific project. VM-to-commitment assignment is read from CommittedResource CRD status (pre-computed by the UsageReconciler). If a CR has no usage status yet, its VMs appear as PAYG until the first reconcile completes (within one CooldownInterval).

type UsageReconciler

type UsageReconciler struct {
	client.Client
	Conf     UsageReconcilerConfig
	VMSource reservations.VMSource
	Monitor  UsageReconcilerMonitor
}

UsageReconciler reconciles CommittedResource.Status usage fields (AssignedInstances, UsedResources, LastUsageReconcileAt) by running the deterministic VM-to-CR assignment periodically and on relevant change events.

func (*UsageReconciler) Reconcile

func (r *UsageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

func (*UsageReconciler) SetupWithManager

func (r *UsageReconciler) SetupWithManager(mgr ctrl.Manager, mcl *multicluster.Client) error

SetupWithManager registers the usage reconciler with the controller manager.

type UsageReconcilerConfig

type UsageReconcilerConfig struct {
	// CooldownInterval is the minimum time between usage reconcile runs for the same CommittedResource.
	// If a reconcile ran within this window, the next trigger is deferred until the window expires.
	// This interval also acts as the periodic fallback: every successful reconcile schedules the
	// next run after this duration so that changes not caught by watches are still picked up.
	CooldownInterval metav1.Duration `json:"cooldownInterval"`
}

UsageReconcilerConfig holds tuning knobs for the usage reconciler.

func DefaultUsageReconcilerConfig

func DefaultUsageReconcilerConfig() UsageReconcilerConfig

func (*UsageReconcilerConfig) ApplyDefaults

func (c *UsageReconcilerConfig) ApplyDefaults()

ApplyDefaults fills in zero-value fields from the defaults, leaving explicitly configured values intact.

type UsageReconcilerMonitor

type UsageReconcilerMonitor struct {
	// contains filtered or unexported fields
}

UsageReconcilerMonitor provides metrics for the usage reconciler.

func NewUsageReconcilerMonitor

func NewUsageReconcilerMonitor() UsageReconcilerMonitor

NewUsageReconcilerMonitor creates a new monitor with Prometheus metrics.

func (UsageReconcilerMonitor) Collect

func (m UsageReconcilerMonitor) Collect(ch chan<- prometheus.Metric)

Collect implements prometheus.Collector.

func (UsageReconcilerMonitor) Describe

func (m UsageReconcilerMonitor) Describe(ch chan<- *prometheus.Desc)

Describe implements prometheus.Collector.

type VMUsageInfo

type VMUsageInfo struct {
	UUID          string
	Name          string
	FlavorName    string
	FlavorGroup   string
	Status        string
	MemoryMB      uint64
	VCPUs         uint64
	DiskGB        uint64
	VideoRAMMiB   *uint64
	OSType        string
	AZ            string
	Hypervisor    string
	CreatedAt     time.Time
	UsageMultiple uint64
}

VMUsageInfo contains VM information needed for usage calculation, enriched with flavor group data.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL