mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-25 16:07:24 +00:00
feat: replicating resources upon namespace creation (#2080)
* feat: replicating resources upon namespace creation This feature enhances the (Global)TenantResource replication by triggering a replication of resources upon a Namespace creation: this speeds up the replication of resources without waiting for the resyncPeriod that could be delayed for several reasons. Signed-off-by: Dario Tranchitella <dario@tranchitella.eu> * fix: addressing fix from github copilot Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Dario Tranchitella <dario@tranchitella.eu> --------- Signed-off-by: Dario Tranchitella <dario@tranchitella.eu> Co-authored-by: Oliver Bähler <26610571+oliverbaehler@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
Oliver Bähler
parent
305cabd4f6
commit
fed61d2815
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020-2026 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package predicates
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
)
|
||||
|
||||
// CreatedAfterPredicate passes the creation of the objects which came to life starting from
|
||||
// the given time, discarding every other event.
|
||||
//
|
||||
// Filtering the event type alone is not enough to react to the new objects only: the
|
||||
// informer replays its initial list as creation events at every start, thus the already
|
||||
// existing objects would be processed again on each restart. Comparing the creation
|
||||
// timestamp discards that replay.
|
||||
//
|
||||
// The creation timestamp is tracked by the API Server with a second granularity: Since
|
||||
// is expected to be truncated accordingly, as NewCreatedAfterPredicate does, otherwise
|
||||
// the objects created within the very same second would be discarded.
|
||||
type CreatedAfterPredicate struct {
|
||||
Since metav1.Time
|
||||
}
|
||||
|
||||
// NewCreatedAfterPredicate builds a predicate passing the objects created starting from the given time.
|
||||
func NewCreatedAfterPredicate(since time.Time) CreatedAfterPredicate {
|
||||
return CreatedAfterPredicate{
|
||||
Since: metav1.NewTime(since.Truncate(time.Second)),
|
||||
}
|
||||
}
|
||||
|
||||
func (p CreatedAfterPredicate) Create(e event.CreateEvent) bool {
|
||||
if e.Object == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
created := e.Object.GetCreationTimestamp()
|
||||
|
||||
return !created.Before(&p.Since)
|
||||
}
|
||||
|
||||
func (p CreatedAfterPredicate) Delete(event.DeleteEvent) bool { return false }
|
||||
|
||||
func (p CreatedAfterPredicate) Update(event.UpdateEvent) bool { return false }
|
||||
|
||||
func (p CreatedAfterPredicate) Generic(event.GenericEvent) bool { return false }
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2020-2026 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package predicates_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/runtime/predicates"
|
||||
)
|
||||
|
||||
func TestCreatedAfterPredicate_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
since := time.Date(2026, time.August, 11, 10, 0, 0, 0, time.UTC)
|
||||
p := predicates.NewCreatedAfterPredicate(since)
|
||||
|
||||
namespace := func(created time.Time) *corev1.Namespace {
|
||||
return &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
CreationTimestamp: metav1.NewTime(created),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
obj *corev1.Namespace
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "created afterwards",
|
||||
obj: namespace(since.Add(time.Second)),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "created within the very same second",
|
||||
obj: namespace(since),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "already existing",
|
||||
obj: namespace(since.Add(-time.Hour)),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "no creation timestamp",
|
||||
obj: &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "example"}},
|
||||
want: false,
|
||||
},
|
||||
} {
|
||||
if got := p.Create(event.CreateEvent{Object: tc.obj}); got != tc.want {
|
||||
t.Fatalf("%s: expected %v, got %v", tc.name, tc.want, got)
|
||||
}
|
||||
}
|
||||
|
||||
if p.Create(event.CreateEvent{}) {
|
||||
t.Fatal("expected a nil object to be discarded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatedAfterPredicate_TruncatesToSecond(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// The API Server tracks the creation timestamp with a second granularity: a Namespace
|
||||
// created right after the start up must not be discarded because of the sub-second
|
||||
// remainder of the lower bound.
|
||||
since := time.Date(2026, time.August, 11, 10, 0, 0, 500_000_000, time.UTC)
|
||||
p := predicates.NewCreatedAfterPredicate(since)
|
||||
|
||||
created := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
CreationTimestamp: metav1.NewTime(since.Truncate(time.Second)),
|
||||
},
|
||||
}
|
||||
|
||||
if !p.Create(event.CreateEvent{Object: created}) {
|
||||
t.Fatal("expected the object created within the same second to be passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatedAfterPredicate_IgnoresAnyOtherEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := predicates.NewCreatedAfterPredicate(time.Date(2026, time.August, 11, 10, 0, 0, 0, time.UTC))
|
||||
|
||||
recent := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
CreationTimestamp: metav1.NewTime(time.Date(2026, time.August, 11, 11, 0, 0, 0, time.UTC)),
|
||||
},
|
||||
}
|
||||
|
||||
if p.Update(event.UpdateEvent{ObjectOld: recent, ObjectNew: recent}) {
|
||||
t.Fatal("expected update events to be discarded")
|
||||
}
|
||||
|
||||
if p.Delete(event.DeleteEvent{Object: recent}) {
|
||||
t.Fatal("expected delete events to be discarded")
|
||||
}
|
||||
|
||||
if p.Generic(event.GenericEvent{Object: recent}) {
|
||||
t.Fatal("expected generic events to be discarded")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user