-
Notifications
You must be signed in to change notification settings - Fork 4
Fix double IP allocation by adding optimistic locking and claimRef identity guard to ipallocator #470
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
base: main
Are you sure you want to change the base?
Fix double IP allocation by adding optimistic locking and claimRef identity guard to ipallocator #470
Changes from all commits
de62675
999ac26
8ce2416
173d17a
4c95219
2f14fd4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,3 +23,4 @@ vendor | |
| *.swp | ||
| *.swo | ||
| *~ | ||
| dev/ | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,7 +5,9 @@ package apiserver | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "log/slog" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "net" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "net/netip" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -16,12 +18,15 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/ironcore-dev/ironcore-net/api/core/v1alpha1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| informers "github.com/ironcore-dev/ironcore-net/client-go/informers/externalversions" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clientset "github.com/ironcore-dev/ironcore-net/client-go/ironcorenet/versioned" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v1alpha1client "github.com/ironcore-dev/ironcore-net/client-go/ironcorenet/versioned/typed/core/v1alpha1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/ironcore-dev/ironcore-net/internal/apiserver" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| netflag "github.com/ironcore-dev/ironcore-net/utils/flag" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/cobra" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/spf13/pflag" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "k8s.io/apimachinery/pkg/runtime" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "k8s.io/apimachinery/pkg/runtime/schema" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "k8s.io/apimachinery/pkg/types" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| utilerrors "k8s.io/apimachinery/pkg/util/errors" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "k8s.io/apiserver/pkg/admission" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| genericapiserver "k8s.io/apiserver/pkg/server" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -163,5 +168,79 @@ func (o *IronCoreNetServerOptions) Run(ctx context.Context) error { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: This is temporary migration code to strip OwnerReferences from legacy ephemeral IPs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Remove this hook once all clusters have been migrated. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| server.GenericAPIServer.AddPostStartHookOrDie("migrate-ephemeral-ip-owner-references", func(hookContext genericapiserver.PostStartHookContext) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ipClient, err := v1alpha1client.NewForConfig(hookContext.LoopbackClientConfig) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slog.Error("Failed to create client for IP migration", "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hookCtx, cancel := context.WithCancel(context.Background()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| go func() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <-hookContext.Done() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancel() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrateEphemeralIPOwnerReferences(hookCtx, ipClient) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+173
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Migration runs synchronously — blocks server readiness until all IPs are processed
The migration is best-effort by design (the TODO comment acknowledges it is temporary), so it is safe to fire it off in a background goroutine and return immediately. ♻️ Proposed fix — run migration asynchronously server.GenericAPIServer.AddPostStartHookOrDie("migrate-ephemeral-ip-owner-references", func(hookContext genericapiserver.PostStartHookContext) error {
ipClient, err := v1alpha1client.NewForConfig(hookContext.LoopbackClientConfig)
if err != nil {
slog.Error("Failed to create client for IP migration", "error", err)
return nil
}
hookCtx, cancel := context.WithCancel(context.Background())
go func() {
+ defer cancel()
<-hookContext.Done()
- cancel()
}()
- migrateEphemeralIPOwnerReferences(hookCtx, ipClient)
+ go migrateEphemeralIPOwnerReferences(hookCtx, ipClient)
return nil
})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return server.GenericAPIServer.PrepareRun().RunWithContext(ctx) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func migrateEphemeralIPOwnerReferences(ctx context.Context, ipClient v1alpha1client.CoreV1alpha1Interface) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continueToken string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrated int | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ipList, err := ipClient.IPs("").List(ctx, metav1.ListOptions{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Limit: 500, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Continue: continueToken, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slog.Error("Failed to list IPs for migration", "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for i := range ipList.Items { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ip := &ipList.Items[i] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if metav1.GetControllerOf(ip) == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| patch := map[string]any{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "metadata": map[string]any{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "ownerReferences": []any{}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "labels": map[string]string{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v1alpha1.IPEphemeralLabel: "true", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| patchData, err := json.Marshal(patch) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slog.Error("Failed to marshal migration patch", "ip", ip.Name, "namespace", ip.Namespace, "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _, err = ipClient.IPs(ip.Namespace).Patch(ctx, ip.Name, types.MergePatchType, patchData, metav1.PatchOptions{}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slog.Error("Failed to migrate IP", "ip", ip.Name, "namespace", ip.Namespace, "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| migrated++ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continueToken = ipList.Continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if continueToken == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if migrated > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| slog.Info("Migrated ephemeral IPs: stripped OwnerReferences and ensured label", "count", migrated) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing
defer cancel()— goroutine lives until server shutdown regardless of whether migration finishedAfter
migrateEphemeralIPOwnerReferencesreturns, the spawned goroutine is still blocked on<-hookContext.Done().cancelis never called on the normal exit path (only when the server shuts down), sohookCtxis never signalled as done after migration completes.go vet/staticcheckflag this as a context leak.Adding
defer cancel()to the goroutine body (as shown in the fix above) ensureshookCtxis cancelled and the goroutine exits once migration is complete.♻️ Proposed fix
hookCtx, cancel := context.WithCancel(context.Background()) go func() { + defer cancel() <-hookContext.Done() - cancel() }()🤖 Prompt for AI Agents