Skip to content
Open
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
89 changes: 53 additions & 36 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,10 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (*
if _, created := observedComposed[r]; created {
f.log.Debug("Processing ", "r:", r)
if in.EnableDeletionSequencing {
of := sequence[i-1]
ofRegex, err := getStrictRegex(string(of))
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot compile regex %s", of))
if err := generateUsagesFromObserved(usages, observedComposed, sequence[i-1], r, in); err != nil {
response.Fatal(rsp, err)
return rsp, nil
}
for k := range desiredComposed {
if ofRegex.MatchString(string(k)) {
if _, ok := observedComposed[k]; ok {
f.log.Debug("Generate Usage", "of:", k, "by r:", r)
usage := GenerateUsage(&observedComposed[k].Resource.Unstructured, &observedComposed[r].Resource.Unstructured, in.ReplayDeletion, in.UsageVersion)
usageComposed := composed.New()
if err := convertViaJSON(usageComposed, usage); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert to JSON %s", usage))
return rsp, err
}
f.log.Debug("created usage", "kind", usageComposed.GetKind(), "name", usageComposed.GetName(), "namespace", usageComposed.GetNamespace())
usages[r+"-"+k+"-usage"] = &resource.DesiredComposed{Resource: usageComposed, Ready: resource.ReadyTrue}
}
}
}
}
// We've already created this resource, so we don't need to do anything.
// We only sequence creation of resources that don't exist yet.
Expand Down Expand Up @@ -142,6 +125,15 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (*
return rsp, nil
}

if in.EnableDeletionSequencing {
// Generate deletion usages from observed resources before any
// creation short-circuiting so dependencies remain stable during teardown.
if err := generateUsagesFromObserved(usages, observedComposed, before, r, in); err != nil {
response.Fatal(rsp, err)
return rsp, nil
}
}

if desired == 0 || desired != readyResources {
// no resource created
msg := fmt.Sprintf("Delaying creation of resource(s) matching %q because %q does not exist yet", r, before)
Expand Down Expand Up @@ -173,23 +165,6 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (*
}
break
}
if in.EnableDeletionSequencing {
for c, o := range observedComposed {
if currentRegex.MatchString(string(c)) && !isUsage(o, in.UsageVersion) {
for _, k := range keys {
f.log.Debug("Generate Usage of ", "k:", k, "by c:", c)
usage := GenerateUsage(&observedComposed[k].Resource.Unstructured, &o.Resource.Unstructured, in.ReplayDeletion, in.UsageVersion)
usageComposed := composed.New()
if err := convertViaJSON(usageComposed, usage); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert to JSON %s", usage))
return rsp, err
}
f.log.Debug("created usage", "kind", usageComposed.GetKind(), "name", usageComposed.GetName(), "namespace", usageComposed.GetNamespace())
usages[c+"-"+k+"-usage"] = &resource.DesiredComposed{Resource: usageComposed, Ready: resource.ReadyTrue}
}
}
}
}
}
}
}
Expand All @@ -198,6 +173,48 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (*
return rsp, response.SetDesiredComposedResources(rsp, desiredComposed)
}

func generateUsagesFromObserved(
usages map[resource.Name]*resource.DesiredComposed,
observedComposed map[resource.Name]resource.ObservedComposed,
ofPattern, byPattern resource.Name,
in *v1beta1.Input,
) error {
ofRegex, err := getStrictRegex(string(ofPattern))
if err != nil {
return errors.Wrapf(err, "cannot compile regex %s", ofPattern)
}
byRegex, err := getStrictRegex(string(byPattern))
if err != nil {
return errors.Wrapf(err, "cannot compile regex %s", byPattern)
}

for byName, byObserved := range observedComposed {
if !byRegex.MatchString(string(byName)) || isUsage(byObserved, in.UsageVersion) {
continue
}
for ofName, ofObserved := range observedComposed {
if !ofRegex.MatchString(string(ofName)) || isUsage(ofObserved, in.UsageVersion) {
continue
}
usage := GenerateUsage(
&ofObserved.Resource.Unstructured,
&byObserved.Resource.Unstructured,
in.ReplayDeletion,
in.UsageVersion,
)
usageComposed := composed.New()
if err := convertViaJSON(usageComposed, usage); err != nil {
return errors.Wrapf(err, "cannot convert to JSON %s", usage)
}
usages[byName+"-"+ofName+"-usage"] = &resource.DesiredComposed{
Resource: usageComposed,
Ready: resource.ReadyTrue,
}
}
}
return nil
}

func getStrictRegex(pattern string) (*regexp.Regexp, error) {
if !strings.HasPrefix(pattern, START) && !strings.HasSuffix(pattern, END) {
// if the user provides a delimited regex, we'll use it as is
Expand Down
73 changes: 73 additions & 0 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1678,6 +1678,79 @@ func TestRunFunction(t *testing.T) {
},
},
},
"DeletionUsageGeneratedWhenReadinessGateShortCircuitsForRegexCurrent": {
reason: "The function should still emit deletion usage edges from observed resources even when creation gating short-circuits for regex current resources",
args: args{
req: &v1.RunFunctionRequest{
Input: resource.MustStructObject(&v1beta1.Input{
EnableDeletionSequencing: true,
ReplayDeletion: true,
Rules: []v1beta1.SequencingRule{
{
Sequence: []resource.Name{
"provider",
"^consumer-.*$",
},
},
},
UsageVersion: v1beta1.UsageV2,
}),
Observed: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{
"provider": {
Resource: resource.MustStructJSON(xr),
Ready: v1.Ready_READY_TRUE,
},
"consumer-a": {
Resource: resource.MustStructJSON(mr),
Ready: v1.Ready_READY_TRUE,
},
},
},
Desired: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{
"consumer-a": {
Resource: resource.MustStructJSON(mr),
Ready: v1.Ready_READY_TRUE,
},
},
},
},
},
want: want{
rsp: &v1.RunFunctionResponse{
Meta: &v1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
Results: []*v1.Result{
{
Severity: v1.Severity_SEVERITY_NORMAL,
Message: "Delaying creation of resource(s) matching \"^consumer-.*$\" because \"provider\" does not exist yet",
Target: &target,
},
},
Desired: &v1.State{
Composite: &v1.Resource{
Resource: resource.MustStructJSON(xr),
},
Resources: map[string]*v1.Resource{
"consumer-a": {
Resource: resource.MustStructJSON(mr),
Ready: v1.Ready_READY_TRUE,
},
"consumer-a-provider-usage": {
Resource: resource.MustStructJSON(uv2),
Ready: v1.Ready_READY_TRUE,
},
},
},
},
},
},
"MarkCompositeNotReady": {
reason: "Set the Composite ready flag to false",
args: args{
Expand Down
Loading