Skip to content

Commit

Permalink
use new 5.x-only method for setting loopback interface addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismarget-j committed Oct 2, 2024
1 parent 35692c6 commit ebcdc9e
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 38 deletions.
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

0 comments on commit ebcdc9e

Please sign in to comment.