mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-21 07:00:37 -04:00
The previous commit arranged for each resource instance object with a planned change to have an execution subgraph generated for it, but didn't honor the dependencies between those objects. There's now a followup loop that adds all of the needed "waiter" edges after the fact, including both the "forward" dependencies between create/update changes and the "reverse" dependencies between delete changes. The shape of the leaf code here got quite messy. In future commits I intend to work on cleaning up the details more, but the main focus here was to restore the execgraph building functionality just enough to prove that this new two-pass planning approach gives us enough information to insert all of the needed dependency edges. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
327 lines
11 KiB
Go
327 lines
11 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package planning
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/engine/internal/exec"
|
|
"github.com/opentofu/opentofu/internal/engine/internal/execgraph"
|
|
"github.com/opentofu/opentofu/internal/plans"
|
|
)
|
|
|
|
// TestExecGraphBuilder_ManagedResourceInstanceSubgraph is a unit test for
|
|
// the ManagedResourceInstanceSubgraph method in particular, focused only on
|
|
// the items and relationships that function produces.
|
|
//
|
|
// Interactions between this method and others should be tested elsewhere.
|
|
func TestExecGraphBuilder_ManagedResourceInstanceSubgraph(t *testing.T) {
|
|
// instAddr is the resource instance address that each test should use
|
|
// for the resource instance object whose result is returned from the
|
|
// "Build" function. We set the return value as the result for this
|
|
// resource instance so that it'll appear in the graph DebugRepr for
|
|
// comparison.
|
|
instAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "placeholder",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
|
|
|
tests := map[string]struct {
|
|
// Build has an awkward signature because this test was written for
|
|
// an older prototype design of ManagedResourceInstanceSubgraph that
|
|
// didn't return as many results.
|
|
// TODO: Find a different way to structure this test so that we can
|
|
// confine this complexity only to the testing loop below and not
|
|
// to each individual test case.
|
|
Build func(
|
|
b *execGraphBuilder,
|
|
providerClientRef execgraph.ResultRef[*exec.ProviderClient],
|
|
) (
|
|
execgraph.ResourceInstanceResultRef,
|
|
execgraph.ResourceInstanceResultRef,
|
|
func(execgraph.AnyResultRef),
|
|
func(execgraph.AnyResultRef),
|
|
)
|
|
WantRepr string
|
|
}{
|
|
"create": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: instAddr,
|
|
Change: plans.Change{
|
|
Action: plans.Create,
|
|
Before: cty.NullVal(cty.EmptyObject),
|
|
After: cty.EmptyObjectVal,
|
|
},
|
|
},
|
|
replaceDestroyThenCreate,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.EmptyObjectVal;
|
|
|
|
r[0] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[1] = ManagedFinalPlan(r[0], nil, v[0], nil);
|
|
r[2] = ManagedApply(r[1], nil, nil, await());
|
|
|
|
test.placeholder = r[2];
|
|
`,
|
|
},
|
|
"update": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: instAddr,
|
|
Change: plans.Change{
|
|
Action: plans.Update,
|
|
Before: cty.StringVal("before"),
|
|
After: cty.StringVal("after"),
|
|
},
|
|
},
|
|
replaceDestroyThenCreate,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.StringVal("after");
|
|
|
|
r[0] = ResourceInstancePrior(test.placeholder);
|
|
r[1] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[2] = ManagedFinalPlan(r[1], r[0], v[0], nil);
|
|
r[3] = ManagedApply(r[2], nil, nil, await());
|
|
|
|
test.placeholder = r[3];
|
|
`,
|
|
},
|
|
"update with move": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
oldInstAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "old",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: oldInstAddr,
|
|
Change: plans.Change{
|
|
Action: plans.Update,
|
|
Before: cty.StringVal("before"),
|
|
After: cty.StringVal("after"),
|
|
},
|
|
},
|
|
replaceDestroyThenCreate,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.StringVal("after");
|
|
|
|
r[0] = ResourceInstancePrior(test.old);
|
|
r[1] = ManagedChangeAddr(r[0], test.placeholder);
|
|
r[2] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[3] = ManagedFinalPlan(r[2], r[1], v[0], nil);
|
|
r[4] = ManagedApply(r[3], nil, nil, await());
|
|
|
|
test.placeholder = r[4];
|
|
`,
|
|
},
|
|
"delete": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: instAddr,
|
|
Change: plans.Change{
|
|
Action: plans.Delete,
|
|
Before: cty.EmptyObjectVal,
|
|
After: cty.NullVal(cty.EmptyObject),
|
|
},
|
|
},
|
|
replaceDestroyThenCreate,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.NullVal(cty.EmptyObject);
|
|
|
|
r[0] = ResourceInstancePrior(test.placeholder);
|
|
r[1] = ManagedFinalPlan(nil, r[0], v[0], nil);
|
|
r[2] = ManagedApply(r[1], nil, nil, await());
|
|
|
|
test.placeholder = nil;
|
|
`,
|
|
},
|
|
"delete then create": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: instAddr,
|
|
Change: plans.Change{
|
|
Action: plans.DeleteThenCreate,
|
|
Before: cty.StringVal("before"),
|
|
After: cty.StringVal("after"),
|
|
},
|
|
},
|
|
replaceDestroyThenCreate,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.StringVal("after");
|
|
v[1] = cty.NullVal(cty.String);
|
|
|
|
r[0] = ResourceInstancePrior(test.placeholder);
|
|
r[1] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[2] = ManagedFinalPlan(r[1], nil, v[0], nil);
|
|
r[3] = ManagedFinalPlan(nil, r[0], v[1], nil);
|
|
r[4] = ManagedApply(r[3], nil, nil, await());
|
|
r[5] = ManagedApply(r[2], nil, nil, await(r[4]));
|
|
|
|
test.placeholder = r[5];
|
|
`,
|
|
},
|
|
"delete then create with move": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
oldInstAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "old",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: oldInstAddr,
|
|
Change: plans.Change{
|
|
Action: plans.DeleteThenCreate,
|
|
Before: cty.StringVal("before"),
|
|
After: cty.StringVal("after"),
|
|
},
|
|
},
|
|
replaceDestroyThenCreate,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.StringVal("after");
|
|
v[1] = cty.NullVal(cty.String);
|
|
|
|
r[0] = ResourceInstancePrior(test.old);
|
|
r[1] = ManagedChangeAddr(r[0], test.placeholder);
|
|
r[2] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[3] = ManagedFinalPlan(r[2], nil, v[0], nil);
|
|
r[4] = ManagedFinalPlan(nil, r[1], v[1], nil);
|
|
r[5] = ManagedApply(r[4], nil, nil, await());
|
|
r[6] = ManagedApply(r[3], nil, nil, await(r[5]));
|
|
|
|
test.placeholder = r[6];
|
|
`,
|
|
},
|
|
"create then delete": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: instAddr,
|
|
Change: plans.Change{
|
|
Action: plans.CreateThenDelete,
|
|
Before: cty.StringVal("before"),
|
|
After: cty.StringVal("after"),
|
|
},
|
|
},
|
|
replaceCreateThenDestroy,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.StringVal("after");
|
|
v[1] = cty.NullVal(cty.String);
|
|
|
|
r[0] = ResourceInstancePrior(test.placeholder);
|
|
r[1] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[2] = ManagedFinalPlan(r[1], nil, v[0], nil);
|
|
r[3] = ManagedFinalPlan(nil, r[0], v[1], nil);
|
|
r[4] = ManagedDepose(r[0], await(r[2], r[3]));
|
|
r[5] = ManagedApply(r[2], r[4], nil, await());
|
|
r[6] = ManagedApply(r[3], nil, nil, await(r[5]));
|
|
|
|
test.placeholder = r[5];
|
|
`,
|
|
},
|
|
"create then delete with move": {
|
|
func(b *execGraphBuilder, providerClientRef execgraph.ResultRef[*exec.ProviderClient]) (execgraph.ResourceInstanceResultRef, execgraph.ResourceInstanceResultRef, func(execgraph.AnyResultRef), func(execgraph.AnyResultRef)) {
|
|
oldInstAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "old",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
|
|
return b.ManagedResourceInstanceSubgraph(
|
|
&plans.ResourceInstanceChange{
|
|
Addr: instAddr,
|
|
PrevRunAddr: oldInstAddr,
|
|
Change: plans.Change{
|
|
Action: plans.CreateThenDelete,
|
|
Before: cty.StringVal("before"),
|
|
After: cty.StringVal("after"),
|
|
},
|
|
},
|
|
replaceCreateThenDestroy,
|
|
providerClientRef,
|
|
)
|
|
},
|
|
`
|
|
v[0] = cty.StringVal("after");
|
|
v[1] = cty.NullVal(cty.String);
|
|
|
|
r[0] = ResourceInstancePrior(test.old);
|
|
r[1] = ManagedChangeAddr(r[0], test.placeholder);
|
|
r[2] = ResourceInstanceDesired(test.placeholder, await());
|
|
r[3] = ManagedFinalPlan(r[2], nil, v[0], nil);
|
|
r[4] = ManagedFinalPlan(nil, r[1], v[1], nil);
|
|
r[5] = ManagedDepose(r[1], await(r[3], r[4]));
|
|
r[6] = ManagedApply(r[3], r[5], nil, await());
|
|
r[7] = ManagedApply(r[4], nil, nil, await(r[6]));
|
|
|
|
test.placeholder = r[6];
|
|
`,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
builder := newExecGraphBuilder()
|
|
// This test is focused only on the resource instance subgraphs,
|
|
// so we just use a placeholder nil result for the provider client.
|
|
providerClientRef := execgraph.NilResultRef[*exec.ProviderClient]()
|
|
// FIXME: We're currently ignoring all but the first result
|
|
// because this test was originally written for an older variant
|
|
// of this function which only had one result. We should find a
|
|
// nice way to restructure this test so that it can check whether
|
|
// _all_ of the return values are correct.
|
|
resultRef, _, _, _ := test.Build(builder, providerClientRef)
|
|
builder.lower.SetResourceInstanceFinalStateResult(instAddr, resultRef)
|
|
|
|
graph := builder.Finish()
|
|
gotGraphRepr := strings.TrimSpace(graph.DebugRepr())
|
|
wantGraphRepr := strings.TrimSpace(stripCommonLeadingTabs(test.WantRepr))
|
|
if diff := cmp.Diff(wantGraphRepr, gotGraphRepr); diff != "" {
|
|
t.Error("wrong result\n" + diff)
|
|
}
|
|
})
|
|
}
|
|
}
|