Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new 5.x methods for setting loopback interface addresses #890

Merged
merged 1 commit into from
Oct 3, 2024
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
137 changes: 102 additions & 35 deletions apstra/blueprint/device_allocation_system_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (
"fmt"
"math"
"net"
"net/netip"
"regexp"
"strconv"

"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/apstra-go-sdk/apstra/enum"
"github.com/Juniper/terraform-provider-apstra/apstra/compatibility"
"github.com/Juniper/terraform-provider-apstra/apstra/constants"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-framework-nettypes/cidrtypes"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
Expand Down Expand Up @@ -204,6 +207,9 @@ func (o *DeviceAllocationSystemAttributes) getLoopback0Addresses(ctx context.Con
}

idx := 0
// todo: Fold getLoopbackInterfaceNode into this function, or at least clean up the
// passing of json.RawMessage because we're only using it for a single purpose now.
// Maybe use this: https://github.com/Juniper/apstra-go-sdk/issues/421
rawJson := getLoopbackInterfaceNode(ctx, bp, nodeId, idx, diags)
if diags.HasError() {
return
Expand Down Expand Up @@ -364,50 +370,72 @@ func (o *DeviceAllocationSystemAttributes) setAsn(ctx context.Context, bp *apstr
}
}

func (o *DeviceAllocationSystemAttributes) setLoopbacks(ctx context.Context, bp *apstra.TwoStageL3ClosClient, nodeId apstra.ObjectId, diags *diag.Diagnostics) {
if !utils.HasValue(o.LoopbackIpv4) && !utils.HasValue(o.LoopbackIpv6) {
return
}
func getLoopbackNodeAndSecurityZoneIDs(ctx context.Context, bp *apstra.TwoStageL3ClosClient, systemNodeId apstra.ObjectId, loopIdx int, diags *diag.Diagnostics) (apstra.ObjectId, apstra.ObjectId) {
query := new(apstra.PathQuery).
SetBlueprintId(bp.Id()).
SetClient(bp.Client()).
Node([]apstra.QEEAttribute{
apstra.NodeTypeSystem.QEEAttribute(),
{Key: "id", Value: apstra.QEStringVal(systemNodeId)},
}).
Out([]apstra.QEEAttribute{apstra.RelationshipTypeHostedInterfaces.QEEAttribute()}).
Node([]apstra.QEEAttribute{
apstra.NodeTypeInterface.QEEAttribute(),
{Key: "if_type", Value: apstra.QEStringVal("loopback")},
{Key: "loopback_id", Value: apstra.QEIntVal(loopIdx)},
{Key: "name", Value: apstra.QEStringVal("n_interface")},
}).
In([]apstra.QEEAttribute{apstra.RelationshipTypeMemberInterfaces.QEEAttribute()}).
Node([]apstra.QEEAttribute{apstra.NodeTypeSecurityZoneInstance.QEEAttribute()}).
In([]apstra.QEEAttribute{apstra.RelationshipTypeInstantiatedBy.QEEAttribute()}).
Node([]apstra.QEEAttribute{
apstra.NodeTypeSecurityZone.QEEAttribute(),
{Key: "name", Value: apstra.QEStringVal("n_security_zone")},
})

idx := 0
rawJson := getLoopbackInterfaceNode(ctx, bp, nodeId, idx, diags)
if diags.HasError() {
return
var queryResponse struct {
Items []struct {
Interface struct {
Id apstra.ObjectId `json:"id"`
} `json:"n_interface"`
SecurityZone struct {
Id apstra.ObjectId `json:"id"`
} `json:"n_security_zone"`
} `json:"items"`
}

if len(rawJson) == 0 && utils.HasValue(o.LoopbackIpv4) {
diags.AddAttributeError(
path.Root("system_attributes").AtName("loopback_ipv4"),
"Cannot set loopback address",
fmt.Sprintf("system %q has no associated loopback %d node -- is it a spine or leaf switch?", nodeId, idx))
}
if len(rawJson) == 0 && utils.HasValue(o.LoopbackIpv6) {
diags.AddAttributeError(
path.Root("system_attributes").AtName("loopback_ipv6"),
"Cannot set loopback address",
fmt.Sprintf("system %q has no associated loopback %d node -- is it a spine or leaf switch?", nodeId, idx))
err := query.Do(ctx, &queryResponse)
if err != nil {
diags.AddError("failed while querying for loopback interface and security zone", err.Error())
return "", ""
}
if diags.HasError() {
return
if len(queryResponse.Items) != 1 {
diags.AddError(
fmt.Sprintf("expected exactly one loopback and security zone node pair, got %d", len(queryResponse.Items)),
fmt.Sprintf("graph query: %q", query),
)
return "", ""
}

var loopbackNode struct {
Id *apstra.ObjectId `json:"id"`
}
ifId, szId := queryResponse.Items[0].Interface.Id, queryResponse.Items[0].SecurityZone.Id

err := json.Unmarshal(rawJson, &loopbackNode)
if err != nil {
diags.AddError(fmt.Sprintf("failed while unpacking system %q loopback %d node", nodeId, idx), err.Error())
return
if ifId == "" {
diags.AddError(
"got empty interface ID",
fmt.Sprintf("graph query: %q", query),
)
}

if loopbackNode.Id == nil {
if szId == "" {
diags.AddError(
fmt.Sprintf("failed parsing loopback %d node linked with node %q", idx, nodeId),
fmt.Sprintf("loopback %d node has no field `id`: %q", idx, string(rawJson)))
return
"got empty security zone ID",
fmt.Sprintf("graph query: %q", query),
)
}

return ifId, szId
}

func (o *DeviceAllocationSystemAttributes) legacySetLoopbacks(ctx context.Context, bp *apstra.TwoStageL3ClosClient, nodeId apstra.ObjectId, diags *diag.Diagnostics) {
patch := &struct {
IPv4Addr string `json:"ipv4_addr,omitempty"`
IPv6Addr string `json:"ipv6_addr,omitempty"`
Expand All @@ -416,9 +444,48 @@ func (o *DeviceAllocationSystemAttributes) setLoopbacks(ctx context.Context, bp
IPv6Addr: o.LoopbackIpv6.ValueString(),
}

err = bp.PatchNode(ctx, *loopbackNode.Id, &patch, nil)
err := bp.PatchNode(ctx, nodeId, &patch, nil)
if err != nil {
diags.AddError(fmt.Sprintf("failed setting loopback addresses to interface node %q", nodeId), err.Error())
return
}
}

func (o *DeviceAllocationSystemAttributes) setLoopbacks(ctx context.Context, bp *apstra.TwoStageL3ClosClient, nodeId apstra.ObjectId, diags *diag.Diagnostics) {
if !utils.HasValue(o.LoopbackIpv4) && !utils.HasValue(o.LoopbackIpv6) {
return
}

idx := 0 // we always are interested in loopback 0

loopbackNodeId, securityZoneId := getLoopbackNodeAndSecurityZoneIDs(ctx, bp, nodeId, idx, diags)
if diags.HasError() {
return
}

if compatibility.ApiNotSupportsSetLoopbackIps.Check(version.Must(version.NewVersion(bp.Client().ApiVersion()))) {
// we must be talking to Apstra 4.x
o.legacySetLoopbacks(ctx, bp, loopbackNodeId, diags)
return
}

// Use new() here to ensure we have invalid non-nil prefix pointers. These will remove values from the API.
ipv4Addr, ipv6Addr := new(netip.Prefix), new(netip.Prefix)
if utils.HasValue(o.LoopbackIpv4) {
ipv4Addr = utils.ToPtr(netip.MustParsePrefix(o.LoopbackIpv4.ValueString()))
}
if utils.HasValue(o.LoopbackIpv6) {
ipv6Addr = utils.ToPtr(netip.MustParsePrefix(o.LoopbackIpv6.ValueString()))
}

err := bp.SetSecurityZoneLoopbacks(ctx, securityZoneId, map[apstra.ObjectId]apstra.SecurityZoneLoopback{
loopbackNodeId: {
IPv4Addr: ipv4Addr,
IPv6Addr: ipv6Addr,
},
})
if err != nil {
diags.AddError(fmt.Sprintf("failed setting loopback addresses to interface node %q", loopbackNode.Id), err.Error())
diags.AddError("failed while setting loopback addresses", err.Error())
return
}
}
Expand Down
1 change: 1 addition & 0 deletions apstra/compatibility/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
)

var (
ApiNotSupportsSetLoopbackIps = versionconstraints.New(apiversions.LtApstra500)
BpIbaDashboardOk = versionconstraints.New(apiversions.LtApstra500)
BpIbaProbeOk = versionconstraints.New(apiversions.LtApstra500)
BpIbaWidgetOk = versionconstraints.New(apiversions.LtApstra500)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ go 1.22.5

require (
github.com/IBM/netaddr v1.5.0
github.com/Juniper/apstra-go-sdk v0.0.0-20240920145043-b30ce0dd776c
github.com/Juniper/apstra-go-sdk v0.0.0-20241001222148-3138d56746ba
github.com/chrismarget-j/go-licenses v0.0.0-20240224210557-f22f3e06d3d4
github.com/chrismarget-j/version-constraints v0.0.0-20240925155624-26771a0a6820
github.com/google/go-cmp v0.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/IBM/netaddr v1.5.0 h1:IJlFZe1+nFs09TeMB/HOP4+xBnX2iM/xgiDOgZgTJq0=
github.com/IBM/netaddr v1.5.0/go.mod h1:DDBPeYgbFzoXHjSz9Jwk7K8wmWV4+a/Kv0LqRnb8we4=
github.com/Juniper/apstra-go-sdk v0.0.0-20240920145043-b30ce0dd776c h1:TbwhSEMYKjH2un1G9tpiv8tsK9M21YPkOZsajAdn9R0=
github.com/Juniper/apstra-go-sdk v0.0.0-20240920145043-b30ce0dd776c/go.mod h1:qXNVTdnVa40aMTOsBTnKoFNYT5ftga2NAkGJhx4o6bY=
github.com/Juniper/apstra-go-sdk v0.0.0-20241001222148-3138d56746ba h1:ceuHpbkoiAKy3CI7pgZk5q6EmYTljFoLBybTOl3pH/A=
github.com/Juniper/apstra-go-sdk v0.0.0-20241001222148-3138d56746ba/go.mod h1:qXNVTdnVa40aMTOsBTnKoFNYT5ftga2NAkGJhx4o6bY=
github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
Expand Down
Loading