diff --git a/pkg/scale/sandboxstore_impl.go b/pkg/scale/sandboxstore_impl.go index 99cd61f..f485b07 100644 --- a/pkg/scale/sandboxstore_impl.go +++ b/pkg/scale/sandboxstore_impl.go @@ -623,6 +623,36 @@ func (p *NodeInventoryPublisher) Publish(ctx context.Context) (int, error) { return len(entries), nil } +var _ InventoryApplier = (*ssaInventoryApplier)(nil) + +type ssaInventoryApplier struct { + c client.Client + fieldOwner string +} + +// NewSSAInventoryApplier returns the default server-side-apply InventoryApplier. +func NewSSAInventoryApplier(c client.Client, fieldOwner string) InventoryApplier { + if fieldOwner == "" { + fieldOwner = "cocoon-node-inventory-publisher" + } + return &ssaInventoryApplier{c: c, fieldOwner: fieldOwner} +} + +func (a *ssaInventoryApplier) Apply(ctx context.Context, inv *NodeInventory) error { + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(inv) + if err != nil { + return fmt.Errorf("scale: encode node inventory: %w", err) + } + u := &unstructured.Unstructured{Object: raw} + u.SetGroupVersionKind(NodeInventoryGVK) + u.SetName(inv.Node) + ac := client.ApplyConfigurationFromUnstructured(u) + if err := a.c.Apply(ctx, ac, client.FieldOwner(a.fieldOwner), client.ForceOwnership); err != nil { + return fmt.Errorf("scale: server-side-apply node inventory: %w", err) + } + return nil +} + var ( _ InventorySource = (*StaticInventorySource)(nil) _ InventoryApplier = (*StaticInventorySource)(nil) diff --git a/pkg/scale/sandboxstore_impl_test.go b/pkg/scale/sandboxstore_impl_test.go index 6707c50..3dff288 100644 --- a/pkg/scale/sandboxstore_impl_test.go +++ b/pkg/scale/sandboxstore_impl_test.go @@ -12,8 +12,11 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" sandboxv1beta1 "github.com/cocoonstack/sandbox-operator/api/v1beta1" + extv1beta1 "github.com/cocoonstack/sandbox-operator/extensions/api/v1beta1" ) func TestScatterGatherList_FlattensAllNodes(t *testing.T) { @@ -271,6 +274,30 @@ func TestWatchSeesAShortLivedSandbox(t *testing.T) { } } +func TestSSAApplier_UpsertsOneObjectPerNode(t *testing.T) { + ctx := t.Context() + cli := fake.NewClientBuilder().WithScheme(newScaleScheme(t)).Build() + pub := NewNodeInventoryPublisher("n1", sliceLive{entry("ns/a", "Running")}, + NewSSAInventoryApplier(cli, "vk-test"), logr.Discard()) + + _, err := pub.Publish(ctx) + require.NoError(t, err) + + got := &extv1beta1.NodeInventory{} + require.NoError(t, cli.Get(ctx, client.ObjectKey{Name: "n1"}, got)) + require.Len(t, got.Entries, 1) + + pub = NewNodeInventoryPublisher("n1", sliceLive{entry("ns/a", "Running"), entry("ns/b", "Running")}, + NewSSAInventoryApplier(cli, "vk-test"), logr.Discard()) + _, err = pub.Publish(ctx) + require.NoError(t, err) + + list := &extv1beta1.NodeInventoryList{} + require.NoError(t, cli.List(ctx, list)) + require.Len(t, list.Items, 1) + assert.Len(t, list.Items[0].Entries, 2) +} + func inv(node string, entries ...InventoryEntry) *NodeInventory { return &NodeInventory{ ObjectMeta: metav1.ObjectMeta{Name: node},