Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ In other words, the following rules apply:

See `example/composition-regex.yaml` for a complete example.

### Function Response Caching

You can set `cacheTTL` to control the Function response cache time-to-live.
This is useful for tuning reconciliation behavior in large compositions.

```yaml
- step: sequence-creation
functionRef:
name: function-sequencer
input:
apiVersion: sequencer.fn.crossplane.io/v1beta1
kind: Input
cacheTTL: 5m
rules:
- sequence:
- first
- second
```

### Composite Readiness
Enabling the `resetCompositeReadiness` flag causes the function to set the Composite's `Ready` flag to `False` when at
least one desired resource is deleted from the request. This prevents the Composite resource from entering the `Ready`
Expand Down
10 changes: 10 additions & 0 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (
"maps"
"regexp"
"strings"
"time"

"github.com/crossplane/crossplane-runtime/v2/pkg/errors"
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
apiextensionsv1beta1 "github.com/crossplane/crossplane/v2/apis/apiextensions/v1beta1"
protectionv1beta1 "github.com/crossplane/crossplane/v2/apis/protection/v1beta1"
"github.com/crossplane/function-sequencer/input/v1beta1"
"google.golang.org/protobuf/types/known/durationpb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

v1 "github.com/crossplane/function-sdk-go/proto/v1"
Expand Down Expand Up @@ -57,6 +59,14 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (*
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
return rsp, nil
}
if in.CacheTTL != "" {
dur, err := time.ParseDuration(in.CacheTTL)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set cacheTTL"))
return rsp, nil
}
rsp.Meta.Ttl = durationpb.New(dur)
}

// Get the desired composed resources from the request.
desiredComposed, err := request.GetDesiredComposedResources(req)
Expand Down
99 changes: 99 additions & 0 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"testing"
"time"

"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
"github.com/crossplane/function-sequencer/input/v1beta1"
Expand Down Expand Up @@ -1769,3 +1770,101 @@ func TestRunFunction(t *testing.T) {
})
}
}

func TestRunFunctionCacheTTL(t *testing.T) {
target := v1.Target_TARGET_COMPOSITE
xr := `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":1}}`

cases := map[string]struct {
reason string
input *v1beta1.Input
want *v1.RunFunctionResponse
}{
"ValidCacheTTL": {
reason: "The function should override the response TTL when cacheTTL is provided",
input: &v1beta1.Input{
CacheTTL: "5m",
Rules: []v1beta1.SequencingRule{
{Sequence: []resource.Name{"first", "second"}},
},
},
want: &v1.RunFunctionResponse{
Meta: &v1.ResponseMeta{Ttl: durationpb.New(5 * time.Minute)},
Results: []*v1.Result{
{
Severity: v1.Severity_SEVERITY_NORMAL,
Message: "Delaying creation of resource(s) matching \"second\" because \"first\" does not exist yet",
Target: &target,
},
},
Desired: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{},
},
},
},
"InvalidCacheTTL": {
reason: "The function should return a fatal result when cacheTTL cannot be parsed",
input: &v1beta1.Input{
CacheTTL: "5x",
Rules: []v1beta1.SequencingRule{
{Sequence: []resource.Name{"first", "second"}},
},
},
want: &v1.RunFunctionResponse{
Meta: &v1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
Results: []*v1.Result{
{
Severity: v1.Severity_SEVERITY_FATAL,
Message: "cannot set cacheTTL: time: unknown unit \"x\" in duration \"5x\"",
Target: &target,
},
},
Desired: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{
"second": {
Resource: resource.MustStructJSON(xr),
},
},
},
},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
f := &Function{log: logging.NewNopLogger()}
req := &v1.RunFunctionRequest{
Input: resource.MustStructObject(tc.input),
Observed: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{},
},
Desired: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{
"second": {
Resource: resource.MustStructJSON(xr),
},
},
},
}
rsp, err := f.RunFunction(context.Background(), req)
if diff := cmp.Diff(tc.want, rsp, protocmp.Transform()); diff != "" {
t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(nil, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff)
}
})
}
}
7 changes: 7 additions & 0 deletions input/v1beta1/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ type Input struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// CacheTTL sets the time-to-live for the Function response.
// Function response caching is an alpha feature in Crossplane and can
// change in future releases.
// +optional
// +kubebuilder:default:="1m"
CacheTTL string `json:"cacheTTL,omitempty"`

// EnableDeletionSequencing controls the automatic creation of Usage/ClusterUsage resources from the dependency tree
// defined by the rule sequences.
// +kubebuilder:object:default=false
Expand Down
6 changes: 6 additions & 0 deletions package/input/sequencer.fn.crossplane.io_inputs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ spec:
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
cacheTTL:
description: |-
CacheTTL sets the time-to-live for the Function response.
Function response caching is an alpha feature in Crossplane and can
change in future releases.
type: string
enableDeletionSequencing:
description: |-
EnableDeletionSequencing controls the automatic creation of Usage/ClusterUsage resources from the dependency tree
Expand Down
Loading