Skip to content

Commit

Permalink
Merge pull request #739 from Juniper/bug/737-subnets-inconsistent-aft…
Browse files Browse the repository at this point in the history
…er-apply

**Breaking Change** eliminate resource pool utilization attributes from resource pool resources
  • Loading branch information
chrismarget-j authored Jul 25, 2024
2 parents 4802b1f + 0bd3c23 commit 5d4c8a3
Show file tree
Hide file tree
Showing 33 changed files with 1,444 additions and 888 deletions.
12 changes: 9 additions & 3 deletions apstra/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ package tfapstra

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/resource"
)

var (
ResourceAgentProfile = resourceAgentProfile{}
ResourceAsnPool = resourceAsnPool{}
ResourceDatacenterGenericSystem = resourceDatacenterGenericSystem{}
ResourceDatacenterIpLinkAddressing = resourceDatacenterIpLinkAddressing{}
ResourceDatacenterRoutingZone = resourceDatacenterRoutingZone{}
ResourceFreeformConfigTemplate = resourceFreeformConfigTemplate{}
ResourceFreeformLink = resourceFreeformLink{}
ResourceFreeformSystem = resourceFreeformSystem{}
ResourceFreeformPropertySet = resourceFreeformPropertySet{}
ResourceFreeformRaGroup = resourceFreeformResourceGroup{}
ResourceFreeformResourceGroup = resourceFreeformResourceGroup{}
ResourceFreeformSystem = resourceFreeformSystem{}
ResourceIntegerPool = resourceIntegerPool{}
ResourceIpv4Pool = resourceIpv4Pool{}
ResourceTemplatePodBased = resourceTemplatePodBased{}
ResourceIpv6Pool = resourceIpv6Pool{}
ResourceTemplateCollapsed = resourceTemplateCollapsed{}
ResourceDatacenterIpLinkAddressing = resourceDatacenterIpLinkAddressing{}
ResourceTemplatePodBased = resourceTemplatePodBased{}
ResourceVniPool = resourceVniPool{}
)

func ResourceName(ctx context.Context, r resource.Resource) string {
Expand Down
8 changes: 4 additions & 4 deletions apstra/resource_agent_profile_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ package tfapstra_test
import (
"context"
"fmt"
"strconv"
"testing"

"github.com/Juniper/apstra-go-sdk/apstra"
tfapstra "github.com/Juniper/terraform-provider-apstra/apstra"
testutils "github.com/Juniper/terraform-provider-apstra/apstra/test_utils"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/stretchr/testify/require"
"strconv"
"testing"
)

const (
Expand Down Expand Up @@ -170,7 +171,6 @@ func TestResourceAgentProfile(t *testing.T) {
for tName, tCase := range testCases {
tName, tCase := tName, tCase
t.Run(tName, func(t *testing.T) {

steps := make([]resource.TestStep, len(tCase.steps))
for i, step := range tCase.steps {
config := step.config.render(resourceType, tName)
Expand All @@ -183,7 +183,7 @@ func TestResourceAgentProfile(t *testing.T) {

// add extractions
for _, ex := range step.extractions {
checks.extractFromState(t, ex.attribute, tNameToApId)
checks.extractFromState(t, resourceType+"."+tName, ex.attribute, tNameToApId)
}

chkLog := checks.string()
Expand Down
56 changes: 11 additions & 45 deletions apstra/resource_asn_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tfapstra
import (
"context"
"fmt"

"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/resources"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
Expand All @@ -12,9 +13,11 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ resource.ResourceWithConfigure = &resourceAsnPool{}
var _ resource.ResourceWithValidateConfig = &resourceAsnPool{}
var _ resourceWithSetClient = &resourceAsnPool{}
var (
_ resource.ResourceWithConfigure = &resourceAsnPool{}
_ resource.ResourceWithValidateConfig = &resourceAsnPool{}
_ resourceWithSetClient = &resourceAsnPool{}
)

type resourceAsnPool struct {
client *apstra.Client
Expand Down Expand Up @@ -114,35 +117,14 @@ func (o *resourceAsnPool) Create(ctx context.Context, req resource.CreateRequest
return
}

// read pool back from Apstra to get usage statistics
var pool *apstra.AsnPool
for {
pool, err = o.client.GetAsnPool(ctx, id)
if err != nil {
if utils.IsApstra404(err) {
resp.Diagnostics.AddAttributeError(
path.Root("id"),
"ASN Pool not found",
fmt.Sprintf("Just-created ASN Pool with ID %q not found", id))
return
}
resp.Diagnostics.AddError("Error retrieving ASN Pool", err.Error())
return
}
if pool.Status != apstra.PoolStatusCreating {
break
}
}

// create state object
var state resources.AsnPool
state.LoadApiData(ctx, pool, &resp.Diagnostics)
plan.Id = types.StringValue(id.String())
plan.SetMutablesToNull(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

// set state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (o *resourceAsnPool) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
Expand Down Expand Up @@ -195,29 +177,13 @@ func (o *resourceAsnPool) Update(ctx context.Context, req resource.UpdateRequest
return
}

// read pool back from Apstra to get usage statistics
p, err := o.client.GetAsnPool(ctx, apstra.ObjectId(plan.Id.ValueString()))
if err != nil {
if utils.IsApstra404(err) {
resp.Diagnostics.AddAttributeError(
path.Root("id"),
"ASN Pool not found",
fmt.Sprintf("Recently updated ASN Pool with ID %q not found", plan.Id.ValueString()))
return
}
resp.Diagnostics.AddError("Error retrieving ASN Pool", err.Error())
return
}

// create new state object
var state resources.AsnPool
state.LoadApiData(ctx, p, &resp.Diagnostics)
plan.SetMutablesToNull(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

// set state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (o *resourceAsnPool) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
Expand Down
224 changes: 224 additions & 0 deletions apstra/resource_asn_pool_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
//go:build integration

package tfapstra_test

import (
"context"
"fmt"
"math/rand"
"sort"
"strconv"
"strings"
"testing"

tfapstra "github.com/Juniper/terraform-provider-apstra/apstra"
testutils "github.com/Juniper/terraform-provider-apstra/apstra/test_utils"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

const (
dataAsnPoolByNameHCL = `
data %q "%s_by_name" {
name = %s
}
`
dataAsnPoolByIdHCL = `
data %q "%s_by_id" {
id = %s
}
`
resourceAsnPoolHCL = `
resource %q %q {
name = "%s"
ranges = [%s
]
}
`
resourceAsnPoolRangeHCL = "\n {first = %d, last = %d},"
)

type asnRange struct {
first int
last int
}

type resourceAsnPool struct {
name string
asnRanges []asnRange
}

func (o resourceAsnPool) render(rType, rName string) string {
asnRanges := make([]string, len(o.asnRanges))
for i, asnRange := range o.asnRanges {
asnRanges[i] = fmt.Sprintf(resourceAsnPoolRangeHCL, asnRange.first, asnRange.last)
}

return "" +
fmt.Sprintf(resourceAsnPoolHCL,
rType, rName,
o.name,
strings.Join(asnRanges, ""),
) +
fmt.Sprintf(dataAsnPoolByIdHCL, rType, rName, fmt.Sprintf("%s.%s.id", rType, rName)) +
fmt.Sprintf(dataAsnPoolByNameHCL, rType, rName, fmt.Sprintf("%s.%s.name", rType, rName))
}

func (o resourceAsnPool) testChecks(t testing.TB, rType, rName string) testChecks {
checks := newTestChecks(rType + "." + rName)

// required and computed attributes can always be checked
checks.append(t, "TestCheckResourceAttrSet", "id")
checks.append(t, "TestCheckResourceAttr", "name", o.name)
checks.append(t, "TestCheckNoResourceAttr", "total")
checks.append(t, "TestCheckNoResourceAttr", "status")
checks.append(t, "TestCheckNoResourceAttr", "used")
checks.append(t, "TestCheckNoResourceAttr", "used_percentage")

checks.append(t, "TestCheckResourceAttr", "ranges.#", strconv.Itoa(len(o.asnRanges)))

for _, r := range o.asnRanges {
checks.appendSetNestedCheck(t, "ranges.*", map[string]string{
"first": strconv.Itoa(r.first),
"last": strconv.Itoa(r.last),
})
}

checks.setPath("data." + rType + "." + rName + "_by_id")
var total int
for _, r := range o.asnRanges {
thisRangeTotal := 1 + r.last - r.first

checks.appendSetNestedCheck(t, "ranges.*", map[string]string{
"first": strconv.Itoa(r.first),
"last": strconv.Itoa(r.last),
"total": strconv.Itoa(thisRangeTotal),
"status": "pool_element_available",
"used": "0",
"used_percentage": "0",
})

total += thisRangeTotal
}

checks.append(t, "TestCheckResourceAttrSet", "id")
checks.append(t, "TestCheckResourceAttr", "name", o.name)
checks.append(t, "TestCheckResourceAttr", "total", strconv.Itoa(total))
checks.append(t, "TestCheckResourceAttr", "status", "not_in_use")
checks.append(t, "TestCheckResourceAttr", "used", "0")
checks.append(t, "TestCheckResourceAttr", "used_percentage", "0")

checks.setPath("data." + rType + "." + rName + "_by_name")
for _, r := range o.asnRanges {
thisRangeTotal := 1 + r.last - r.first

checks.appendSetNestedCheck(t, "ranges.*", map[string]string{
"first": strconv.Itoa(r.first),
"last": strconv.Itoa(r.last),
"total": strconv.Itoa(thisRangeTotal),
"status": "pool_element_available",
"used": "0",
"used_percentage": "0",
})
}

checks.append(t, "TestCheckResourceAttrSet", "id")
checks.append(t, "TestCheckResourceAttr", "name", o.name)
checks.append(t, "TestCheckResourceAttr", "total", strconv.Itoa(total))
checks.append(t, "TestCheckResourceAttr", "status", "not_in_use")
checks.append(t, "TestCheckResourceAttr", "used", "0")
checks.append(t, "TestCheckResourceAttr", "used_percentage", "0")

return checks
}

func TestAccResourceAsnPool(t *testing.T) {
ctx := context.Background()
client := testutils.GetTestClient(t, ctx)
apiVersion := version.Must(version.NewVersion(client.ApiVersion()))

randomRanges := func(t testing.TB, count int) []asnRange {
ints := randIntSet(t, 10000, 19999, count*2)
sort.Ints(ints)

result := make([]asnRange, count)
for i := range count {
result[i] = asnRange{
first: ints[(2 * i)],
last: ints[(2*i)+1],
}
}

return result
}

type testStep struct {
config resourceAsnPool
}

type testCase struct {
apiVersionConstraints version.Constraints
steps []testStep
}

testCases := map[string]testCase{
"simple_case": {
steps: []testStep{
{
config: resourceAsnPool{
name: acctest.RandString(6),
asnRanges: randomRanges(t, rand.Intn(5)+1),
},
},
{
config: resourceAsnPool{
name: acctest.RandString(6),
asnRanges: randomRanges(t, rand.Intn(5)+1),
},
},
{
config: resourceAsnPool{
name: acctest.RandString(6),
asnRanges: randomRanges(t, rand.Intn(5)+1),
},
},
},
},
}

resourceType := tfapstra.ResourceName(ctx, &tfapstra.ResourceAsnPool)

for tName, tCase := range testCases {
tName, tCase := tName, tCase
t.Run(tName, func(t *testing.T) {
t.Parallel()
if !tCase.apiVersionConstraints.Check(apiVersion) {
t.Skipf("test case %s requires Apstra %s", tName, tCase.apiVersionConstraints.String())
}

steps := make([]resource.TestStep, len(tCase.steps))
for i, step := range tCase.steps {
config := step.config.render(resourceType, tName)
checks := step.config.testChecks(t, resourceType, tName)

chkLog := checks.string()
stepName := fmt.Sprintf("test case %q step %d", tName, i+1)

t.Logf("\n// ------ begin config for %s ------\n%s// -------- end config for %s ------\n\n", stepName, config, stepName)
t.Logf("\n// ------ begin checks for %s ------\n%s// -------- end checks for %s ------\n\n", stepName, chkLog, stepName)

steps[i] = resource.TestStep{
Config: insecureProviderConfigHCL + config,
Check: resource.ComposeAggregateTestCheckFunc(checks.checks...),
}
}

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: steps,
})
})
}
}
Loading

0 comments on commit 5d4c8a3

Please sign in to comment.