Repository navigation
feat(reservations): steer CR VM live migrations toward reservation slot hosts #981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
7b7d92f
new filter logic
juliusclausnitzer acf8334
adding filter to pipelines
juliusclausnitzer 27c750b
add test
juliusclausnitzer 1b66fd2
lint
juliusclausnitzer 62e97e4
fix
juliusclausnitzer f88d377
lint
juliusclausnitzer 3746305
test refactor
juliusclausnitzer 57cb537
pivot to weigher instead of filter
juliusclausnitzer 4663625
refine
juliusclausnitzer e6ef008
review fixes
juliusclausnitzer 43b8806
fix lint
juliusclausnitzer 08fc90f
lint
juliusclausnitzer 5dff192
Merge branch 'main' into feat/213-cr-migration-slot-filter
mblos 50d020f
lint
juliusclausnitzer 83a66db
Merge branch 'main' into feat/213-cr-migration-slot-filter
juliusclausnitzer a542d89
remove metric
juliusclausnitzer 3ae6a26
Merge branch 'main' into feat/213-cr-migration-slot-filter
juliusclausnitzer ee5bd4b
Merge remote-tracking branch 'origin/main' into feat/213-cr-migration…
juliusclausnitzer 819d19b
Merge branch 'main' into feat/213-cr-migration-slot-filter
juliusclausnitzer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
internal/scheduling/nova/plugins/weighers/kvm_cr_migration_slot.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| // Copyright SAP SE | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package weighers | ||
|
|
||
| import ( | ||
| "context" | ||
| "log/slog" | ||
|
|
||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
|
|
||
| api "github.com/cobaltcore-dev/cortex/api/external/nova" | ||
| "github.com/cobaltcore-dev/cortex/api/v1alpha1" | ||
| "github.com/cobaltcore-dev/cortex/internal/scheduling/lib" | ||
| "github.com/cobaltcore-dev/cortex/internal/scheduling/nova/crs" | ||
| hv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1" | ||
| ) | ||
|
|
||
| // Options for the KVM CR migration slot weigher. | ||
| type KVMCRMigrationSlotOpts struct { | ||
| // Weight assigned to hosts that can accommodate the CR reservation slot. | ||
| // Default: 0.1 | ||
| SlotHostWeight *float64 `json:"slotHostWeight,omitempty"` | ||
| // Weight assigned to hosts that cannot accommodate the CR reservation slot. | ||
| // Default: 0.0 | ||
| DefaultHostWeight *float64 `json:"defaultHostWeight,omitempty"` | ||
| } | ||
|
|
||
| func (o KVMCRMigrationSlotOpts) Validate() error { | ||
| return nil | ||
| } | ||
|
|
||
| func (o KVMCRMigrationSlotOpts) GetSlotHostWeight() float64 { | ||
| if o.SlotHostWeight == nil { | ||
| return 0.1 | ||
| } | ||
| return *o.SlotHostWeight | ||
| } | ||
|
|
||
| func (o KVMCRMigrationSlotOpts) GetDefaultHostWeight() float64 { | ||
| if o.DefaultHostWeight == nil { | ||
| return 0.0 | ||
| } | ||
| return *o.DefaultHostWeight | ||
| } | ||
|
|
||
| // KVMCRMigrationSlotStep weighs live-migration candidates by whether they can | ||
| // accommodate the CR reservation slot of the migrating VM. | ||
| // | ||
| // When a VM with a CR reservation slot is migrated, this weigher boosts hosts | ||
| // that either have a ready CR reservation with sufficient remaining capacity for | ||
| // the slot, or have enough free memory for the slot to follow via the reconciler. | ||
| // This steers the migration toward hosts where double-blocking is avoided or | ||
| // minimised. | ||
| // | ||
| // If the VM has no confirmed CR reservation, all candidates receive equal weight | ||
| // (no effect on ranking). | ||
| // | ||
| // Only activates for LiveMigrationIntent. | ||
| type KVMCRMigrationSlotStep struct { | ||
| lib.BaseWeigher[api.ExternalSchedulerRequest, KVMCRMigrationSlotOpts] | ||
| } | ||
|
|
||
| func (s *KVMCRMigrationSlotStep) Run( | ||
| traceLog *slog.Logger, | ||
| request api.ExternalSchedulerRequest, | ||
| ) (*lib.FilterWeigherPipelineStepResult, error) { | ||
|
|
||
| result := s.IncludeAllHostsFromRequest(request) | ||
|
|
||
| intent, err := request.GetIntent() | ||
| if err != nil || intent != api.LiveMigrationIntent { | ||
| traceLog.Info("not a live migration, skipping CR migration slot weigher") | ||
| return result, nil //nolint:nilerr | ||
| } | ||
|
|
||
| instanceUUID := request.Spec.Data.InstanceUUID | ||
| projectID := request.Spec.Data.ProjectID | ||
|
|
||
| var allReservations v1alpha1.ReservationList | ||
| if err := s.Client.List(context.Background(), &allReservations, | ||
| client.MatchingLabels{v1alpha1.LabelReservationType: v1alpha1.ReservationTypeLabelCommittedResource}, | ||
| ); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Find the source slot that has this VM confirmed. | ||
| var sourceSlot *v1alpha1.Reservation | ||
| for i := range allReservations.Items { | ||
| res := &allReservations.Items[i] | ||
| if !res.IsReady() { | ||
| continue | ||
| } | ||
| if res.Status.CommittedResourceReservation == nil || res.Spec.CommittedResourceReservation == nil { | ||
| continue | ||
| } | ||
| if _, ok := res.Status.CommittedResourceReservation.Allocations[instanceUUID]; ok { | ||
| sourceSlot = res | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if sourceSlot == nil { | ||
| traceLog.Info("migrating VM has no confirmed CR reservation slot, skipping slot weigher", | ||
| "instanceUUID", instanceUUID) | ||
| return result, nil | ||
| } | ||
|
|
||
| slotMemoryBytes := sourceSlot.Spec.Resources[hv1.ResourceMemory] | ||
| if slotMemoryBytes.IsZero() { | ||
| traceLog.Info("source CR slot has no memory resource, skipping slot weigher", | ||
| "instanceUUID", instanceUUID, | ||
| "reservation", sourceSlot.Name) | ||
| return result, nil | ||
| } | ||
|
|
||
| resourceGroup := sourceSlot.Spec.CommittedResourceReservation.ResourceGroup | ||
|
|
||
| traceLog.Info("found source CR reservation slot for migrating VM", | ||
| "instanceUUID", instanceUUID, | ||
| "reservation", sourceSlot.Name, | ||
| "slotMemoryBytes", slotMemoryBytes.Value(), | ||
| "resourceGroup", resourceGroup, | ||
| ) | ||
|
|
||
| evaluator, err := crs.BuildSlotEvaluatorFromReservations(context.Background(), s.Client, allReservations.Items) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| slotHostWeight := s.Options.GetSlotHostWeight() | ||
| defaultHostWeight := s.Options.GetDefaultHostWeight() | ||
|
|
||
| for host := range result.Activations { | ||
| hasSlot := evaluator.HasSlotWithCapacity(host, projectID, resourceGroup, slotMemoryBytes.Value()) | ||
| canFit := evaluator.CanAccommodateSlot(host, slotMemoryBytes.Value()) | ||
| switch { | ||
| case hasSlot: | ||
| result.Activations[host] = slotHostWeight | ||
| traceLog.Info("host has existing CR slot for migration, boosting weight", | ||
| "host", host, "weight", slotHostWeight) | ||
| case canFit: | ||
| result.Activations[host] = slotHostWeight | ||
| traceLog.Info("host can accommodate slot via reconciler, boosting weight", | ||
| "host", host, "weight", slotHostWeight) | ||
| default: | ||
| result.Activations[host] = defaultHostWeight | ||
| traceLog.Info("host cannot accommodate CR slot, applying low weight", | ||
| "host", host, "weight", defaultHostWeight) | ||
| } | ||
| } | ||
|
|
||
| return result, nil | ||
| } | ||
|
|
||
| func init() { | ||
| Index["kvm_cr_migration_slot"] = func() NovaWeigher { | ||
| return &KVMCRMigrationSlotStep{} | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.