Skip to content

Commit b4bf132

Browse files
committed
cache mutators at runtime to handle expensive to compile transformers
On-behalf-of: @SAP [email protected]
1 parent 3d54350 commit b4bf132

File tree

6 files changed

+53
-34
lines changed

6 files changed

+53
-34
lines changed

internal/controller/sync/controller.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,7 @@ func Create(
103103
remoteDummy.SetGroupVersionKind(remoteGVK)
104104

105105
// create the syncer that holds the meat&potatoes of the synchronization logic
106-
mutator, err := mutation.NewMutator(pubRes.Spec.Mutation)
107-
if err != nil {
108-
return nil, fmt.Errorf("failed to create mutator: %w", err)
109-
}
110-
111-
syncer, err := sync.NewResourceSyncer(log, localManager.GetClient(), virtualWorkspaceCluster.GetClient(), pubRes, localCRD, mutator, stateNamespace, agentName)
106+
syncer, err := sync.NewResourceSyncer(log, localManager.GetClient(), virtualWorkspaceCluster.GetClient(), pubRes, localCRD, mutation.NewMutator, stateNamespace, agentName)
112107
if err != nil {
113108
return nil, fmt.Errorf("failed to create syncer: %w", err)
114109
}

internal/mutation/mutator.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,18 @@ var _ Mutator = &mutator{}
4747
// NewMutator creates a new mutator, which will apply the mutation rules to a synced object, in
4848
// both directions. A nil spec is supported and will simply make the mutator not do anything.
4949
func NewMutator(spec *syncagentv1alpha1.ResourceMutationSpec) (Mutator, error) {
50-
var (
51-
specAgg *transformer.AggregateTransformer
52-
statusAgg *transformer.AggregateTransformer
53-
err error
54-
)
55-
56-
if spec != nil {
57-
specAgg, err = createAggregatedTransformer(spec.Spec)
58-
if err != nil {
59-
return nil, fmt.Errorf("cannot create transformer for spec: %w", err)
60-
}
50+
if spec == nil {
51+
return nil, nil
52+
}
6153

62-
statusAgg, err = createAggregatedTransformer(spec.Status)
63-
if err != nil {
64-
return nil, fmt.Errorf("cannot create transformer for status: %w", err)
65-
}
54+
specAgg, err := createAggregatedTransformer(spec.Spec)
55+
if err != nil {
56+
return nil, fmt.Errorf("cannot create transformer for spec: %w", err)
57+
}
58+
59+
statusAgg, err := createAggregatedTransformer(spec.Status)
60+
if err != nil {
61+
return nil, fmt.Errorf("cannot create transformer for status: %w", err)
6662
}
6763

6864
return &mutator{
@@ -72,10 +68,18 @@ func NewMutator(spec *syncagentv1alpha1.ResourceMutationSpec) (Mutator, error) {
7268
}
7369

7470
func (m *mutator) MutateSpec(toMutate *unstructured.Unstructured, otherObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
71+
if m == nil {
72+
return toMutate, nil
73+
}
74+
7575
return m.spec.Apply(toMutate, otherObj)
7676
}
7777

7878
func (m *mutator) MutateStatus(toMutate *unstructured.Unstructured, otherObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
79+
if m == nil {
80+
return toMutate, nil
81+
}
82+
7983
return m.status.Apply(toMutate, otherObj)
8084
}
8185

internal/mutation/transformer/template_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/kcp-dev/api-syncagent/internal/test/diff"
2323
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
2424
"github.com/kcp-dev/api-syncagent/test/utils"
25+
2526
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2627
)
2728

internal/sync/syncer.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,25 @@ type ResourceSyncer struct {
4545

4646
destDummy *unstructured.Unstructured
4747

48-
mutator mutation.Mutator
48+
// cached mutators (for those transformers that are expensive to compile, like CEL)
49+
primaryMutator mutation.Mutator
50+
relatedMutators map[string]mutation.Mutator
4951

5052
agentName string
5153

5254
// newObjectStateStore is used for testing purposes
5355
newObjectStateStore newObjectStateStoreFunc
5456
}
5557

58+
type MutatorCreatorFunc func(*syncagentv1alpha1.ResourceMutationSpec) (mutation.Mutator, error)
59+
5660
func NewResourceSyncer(
5761
log *zap.SugaredLogger,
5862
localClient ctrlruntimeclient.Client,
5963
remoteClient ctrlruntimeclient.Client,
6064
pubRes *syncagentv1alpha1.PublishedResource,
6165
localCRD *apiextensionsv1.CustomResourceDefinition,
62-
mutator mutation.Mutator,
66+
mutatorCreator MutatorCreatorFunc,
6367
stateNamespace string,
6468
agentName string,
6569
) (*ResourceSyncer, error) {
@@ -94,6 +98,21 @@ func NewResourceSyncer(
9498
}
9599
}
96100

101+
primaryMutator, err := mutatorCreator(pubRes.Spec.Mutation)
102+
if err != nil {
103+
return nil, fmt.Errorf("failed to create primary object mutator: %w", err)
104+
}
105+
106+
relatedMutators := map[string]mutation.Mutator{}
107+
for _, rr := range pubRes.Spec.Related {
108+
mutator, err := mutatorCreator(rr.Mutation)
109+
if err != nil {
110+
return nil, fmt.Errorf("failed to create related object %q mutator: %w", rr.Identifier, err)
111+
}
112+
113+
relatedMutators[rr.Identifier] = mutator
114+
}
115+
97116
return &ResourceSyncer{
98117
log: log.With("local-gvk", localGVK, "remote-gvk", remoteGVK),
99118
localClient: localClient,
@@ -102,7 +121,8 @@ func NewResourceSyncer(
102121
localCRD: localCRD,
103122
subresources: subresources,
104123
destDummy: localDummy,
105-
mutator: mutator,
124+
primaryMutator: primaryMutator,
125+
relatedMutators: relatedMutators,
106126
agentName: agentName,
107127
newObjectStateStore: newKubernetesStateStoreCreator(stateNamespace),
108128
}, nil
@@ -162,7 +182,7 @@ func (s *ResourceSyncer) Process(ctx Context, remoteObj *unstructured.Unstructur
162182
// in kcp is deleted
163183
blockSourceDeletion: true,
164184
// use the configured mutations from the PublishedResource
165-
mutator: s.mutator,
185+
mutator: s.primaryMutator,
166186
// make sure the syncer can remember the current state of any object
167187
stateStore: stateStore,
168188
// For the main resource, we need to store metadata on the destination copy

internal/sync/syncer_related.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"github.com/tidwall/gjson"
2828
"go.uber.org/zap"
2929

30-
"github.com/kcp-dev/api-syncagent/internal/mutation"
3130
"github.com/kcp-dev/api-syncagent/internal/sync/templating"
3231
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
3332

@@ -118,11 +117,6 @@ func (s *ResourceSyncer) processRelatedResource(log *zap.SugaredLogger, stateSto
118117
object: destObject,
119118
}
120119

121-
mutator, err := mutation.NewMutator(relRes.Mutation)
122-
if err != nil {
123-
return false, fmt.Errorf("failed to create mutator: %w", err)
124-
}
125-
126120
syncer := objectSyncer{
127121
// Related objects within kcp are not labelled with the agent name because it's unnecessary.
128122
// agentName: "",
@@ -147,7 +141,7 @@ func (s *ResourceSyncer) processRelatedResource(log *zap.SugaredLogger, stateSto
147141
// sure we can clean up properly
148142
blockSourceDeletion: relRes.Origin == "kcp",
149143
// apply mutation rules configured for the related resource
150-
mutator: mutator,
144+
mutator: s.relatedMutators[relRes.Identifier],
151145
// we never want to store sync-related metadata inside kcp
152146
metadataOnDestination: false,
153147
}

internal/sync/syncer_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/kcp-dev/logicalcluster/v3"
2727
"go.uber.org/zap"
2828

29+
"github.com/kcp-dev/api-syncagent/internal/mutation"
2930
dummyv1alpha1 "github.com/kcp-dev/api-syncagent/internal/sync/apis/dummy/v1alpha1"
3031
"github.com/kcp-dev/api-syncagent/internal/test/diff"
3132
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
@@ -899,7 +900,9 @@ func TestSyncerProcessingSingleResourceWithoutStatus(t *testing.T) {
899900
remoteClient,
900901
testcase.pubRes,
901902
testcase.localCRD,
902-
nil,
903+
func(rms *syncagentv1alpha1.ResourceMutationSpec) (mutation.Mutator, error) {
904+
return nil, nil
905+
},
903906
stateNamespace,
904907
"textor-the-doctor",
905908
)
@@ -1205,7 +1208,9 @@ func TestSyncerProcessingSingleResourceWithStatus(t *testing.T) {
12051208
remoteClient,
12061209
testcase.pubRes,
12071210
testcase.localCRD,
1208-
nil,
1211+
func(rms *syncagentv1alpha1.ResourceMutationSpec) (mutation.Mutator, error) {
1212+
return nil, nil
1213+
},
12091214
stateNamespace,
12101215
"textor-the-doctor",
12111216
)

0 commit comments

Comments
 (0)