mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-03-21 07:00:37 -04:00
The first pass in the planning engine decides what we're now calling the "initial replace order" for each object, which can either be replaceAnyOrder or replaceCreateThenDestroy based purely on whether that object was configured with "create_before_destroy = true" or not. The second pass then analyses the materialized graph of resource instance objects to translate any replaceAnyOrder objects into either replaceDestroyThenCreate or replaceCreateThenDestroy depending on whether they are in any dependency chain with something else that requires replaceCreateThenDestroy. For now we don't use the results of this for anything, but in a future commit we'll use it to select the appropriate ordering for the apply-time operations in the execution graph. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
284 lines
9.0 KiB
Go
284 lines
9.0 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 (
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
)
|
|
|
|
func TestFindEffectiveReplaceOrders(t *testing.T) {
|
|
// TODO: This test is currently just a stub of some simple cases to
|
|
// illustrate what [findEffectiveReplaceOrders] is intended to do.
|
|
// If this function survives into a shipping form of the new planning
|
|
// engine then we should consider whether there are any other cases we
|
|
// should cover here.
|
|
|
|
objAddr := func(k string) addrs.AbsResourceInstanceObject {
|
|
return addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "test",
|
|
}.Instance(addrs.StringKey(k)).Absolute(addrs.RootModuleInstance).CurrentObject()
|
|
}
|
|
|
|
tests := map[string]struct {
|
|
build func(*resourceInstanceObjectsBuilder)
|
|
want addrs.Map[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]
|
|
wantSelfDeps addrs.Set[addrs.AbsResourceInstanceObject]
|
|
}{
|
|
"empty": {
|
|
func(objs *resourceInstanceObjectsBuilder) {},
|
|
addrs.MakeMap[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder](),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"one allowing any order": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
},
|
|
addrs.MakeMap(addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceDestroyThenCreate,
|
|
}),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"one requiring create then destroy": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceCreateThenDestroy,
|
|
})
|
|
},
|
|
addrs.MakeMap(addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceCreateThenDestroy,
|
|
}),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"chain with everything allowing any order": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("b"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("c"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a"), objAddr("b")),
|
|
})
|
|
},
|
|
addrs.MakeMap(
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("b"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("c"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"chain with leader requiring create then destroy": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceCreateThenDestroy,
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("b"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("c"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a"), objAddr("b")),
|
|
})
|
|
},
|
|
addrs.MakeMap(
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("b"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("c"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"chain with middle requiring create then destroy": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("b"),
|
|
ReplaceOrder: replaceCreateThenDestroy,
|
|
Dependencies: addrs.MakeSet(objAddr("a")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("c"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a"), objAddr("b")),
|
|
})
|
|
},
|
|
addrs.MakeMap(
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("b"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("c"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"chain with trailer requiring create then destroy": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("b"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("c"),
|
|
ReplaceOrder: replaceCreateThenDestroy,
|
|
Dependencies: addrs.MakeSet(objAddr("a"), objAddr("b")),
|
|
})
|
|
},
|
|
addrs.MakeMap(
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("b"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("c"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"unchained object unaffected": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("b"),
|
|
ReplaceOrder: replaceCreateThenDestroy,
|
|
Dependencies: addrs.MakeSet(objAddr("a")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("unchained"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
},
|
|
addrs.MakeMap(
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("b"),
|
|
Value: replaceCreateThenDestroy,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("unchained"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
),
|
|
addrs.MakeSet[addrs.AbsResourceInstanceObject](),
|
|
},
|
|
"self-dependency": {
|
|
func(objs *resourceInstanceObjectsBuilder) {
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("a"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a"), objAddr("b")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("b"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
Dependencies: addrs.MakeSet(objAddr("a"), objAddr("b")),
|
|
})
|
|
objs.Put(&resourceInstanceObject{
|
|
Addr: objAddr("c"),
|
|
ReplaceOrder: replaceAnyOrder,
|
|
})
|
|
},
|
|
addrs.MakeMap(
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("a"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("b"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
addrs.MapElem[addrs.AbsResourceInstanceObject, resourceInstanceReplaceOrder]{
|
|
Key: objAddr("c"),
|
|
Value: replaceDestroyThenCreate,
|
|
},
|
|
),
|
|
addrs.MakeSet(
|
|
objAddr("a"),
|
|
objAddr("b"),
|
|
),
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
builder := newResourceInstanceObjectsBuilder()
|
|
test.build(builder)
|
|
objs := builder.Close()
|
|
|
|
got, gotSelfDeps := findEffectiveReplaceOrders(objs)
|
|
if diff := cmp.Diff(test.want, got); diff != "" {
|
|
t.Error("wrong result\n" + diff)
|
|
}
|
|
if diff := cmp.Diff(test.wantSelfDeps, gotSelfDeps); diff != "" {
|
|
t.Error("wrong self-dependencies\n" + diff)
|
|
}
|
|
})
|
|
}
|
|
}
|