Skip to content

Commit

Permalink
Merge pull request #430 from Juniper/feat/429-junos_evpn_irb_mode
Browse files Browse the repository at this point in the history
Add support for `junos_evpn_irb_mode` to existing Routing Zone resource and data sources
  • Loading branch information
chrismarget-j authored Nov 8, 2023
2 parents dfa40b1 + 0ee8fcb commit 953e13d
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 33 deletions.
2 changes: 2 additions & 0 deletions apstra/blueprint/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const (
errApiGetWithTypeAndId = "API error getting %s %q"
errApiPatchWithTypeAndId = "API error patching %s %q"
errProviderBug = "Provider Bug. Please report this issue to the provider maintainers."
errApiCompatibility = "Apstra API version incompatibility"
errInvalidConfig = "invalid configuration"

ErrDCBlueprintCreate = "Failed to create client for Datacenter Blueprint %s"
)
66 changes: 49 additions & 17 deletions apstra/blueprint/datacenter_routing_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import (
"regexp"
)

const (
errInvalidConfig = "invalid configuration"
)
var junosEvpnIrbModeDefault = apstra.JunosEvpnIrbModeAsymmetric.Value

type DatacenterRoutingZone struct {
Id types.String `tfsdk:"id"`
Expand All @@ -39,10 +37,10 @@ type DatacenterRoutingZone struct {
RoutingPolicyId types.String `tfsdk:"routing_policy_id"`
ImportRouteTargets types.Set `tfsdk:"import_route_targets"`
ExportRouteTargets types.Set `tfsdk:"export_route_targets"`
JunosEvpnIrbMode types.String `tfsdk:"junos_evpn_irb_mode"`
}

func (o DatacenterRoutingZone) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
nameRE := regexp.MustCompile("^[A-Za-z0-9_-]+$")
return map[string]dataSourceSchema.Attribute{
"id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Apstra graph node ID. Required when `name` is omitted.",
Expand All @@ -67,7 +65,8 @@ func (o DatacenterRoutingZone) DataSourceAttributes() map[string]dataSourceSchem
Optional: true,
Validators: []validator.String{
stringvalidator.LengthBetween(1, 17),
stringvalidator.RegexMatches(nameRE, "only underscore, dash and alphanumeric characters allowed."),
stringvalidator.RegexMatches(regexp.MustCompile("^[A-Za-z0-9_-]+$"),
"only underscore, dash and alphanumeric characters allowed."),
},
},
"vlan_id": dataSourceSchema.Int64Attribute{
Expand Down Expand Up @@ -113,6 +112,12 @@ func (o DatacenterRoutingZone) DataSourceAttributes() map[string]dataSourceSchem
Computed: true,
ElementType: types.StringType,
},
"junos_evpn_irb_mode": dataSourceSchema.StringAttribute{
MarkdownDescription: "Symmetric IRB Routing for EVPN on Junos devices makes use of an L3 VNI for " +
"inter-subnet routing which is embedded into EVPN Type2-routes to support better scaling for " +
"networks with large amounts of VLANs.",
Computed: true,
},
}
}

Expand Down Expand Up @@ -157,23 +162,26 @@ func (o DatacenterRoutingZone) DataSourceFilterAttributes() map[string]dataSourc
"Set this attribute in an EVPN blueprint to use a non-default policy.",
Optional: true,
},
"import_route_targets": resourceSchema.SetAttribute{
"import_route_targets": dataSourceSchema.SetAttribute{
MarkdownDescription: "This is a set of *required* RTs, not an exact-match list.",
Optional: true,
ElementType: types.StringType,
Validators: []validator.Set{setvalidator.SizeAtLeast(1)},
},
"export_route_targets": resourceSchema.SetAttribute{
"export_route_targets": dataSourceSchema.SetAttribute{
MarkdownDescription: "This is a set of *required* RTs, not an exact-match list.",
Optional: true,
ElementType: types.StringType,
Validators: []validator.Set{setvalidator.SizeAtLeast(1)},
},
"junos_evpn_irb_mode": dataSourceSchema.StringAttribute{
MarkdownDescription: "Symmetric IRB Routing for EVPN on Junos devices makes use of an L3 VNI for " +
"inter-subnet routing which is embedded into EVPN Type2-routes to support better scaling for " +
"networks with large amounts of VLANs.",
Optional: true,
},
}
}

func (o DatacenterRoutingZone) ResourceAttributes() map[string]resourceSchema.Attribute {
nameRE := regexp.MustCompile("^[A-Za-z0-9_-]+$")
return map[string]resourceSchema.Attribute{
"id": resourceSchema.StringAttribute{
MarkdownDescription: "Apstra graph node ID.",
Expand All @@ -190,7 +198,8 @@ func (o DatacenterRoutingZone) ResourceAttributes() map[string]resourceSchema.At
MarkdownDescription: "VRF name displayed in the Apstra web UI.",
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(nameRE, "only underscore, dash and alphanumeric characters allowed."),
stringvalidator.RegexMatches(regexp.MustCompile("^[A-Za-z0-9_-]+$"),
"only underscore, dash and alphanumeric characters allowed."),
stringvalidator.LengthBetween(1, 15),
},
},
Expand Down Expand Up @@ -253,6 +262,19 @@ func (o DatacenterRoutingZone) ResourceAttributes() map[string]resourceSchema.At
setvalidator.ValueStringsAre(apstravalidator.ParseRT()),
},
},
"junos_evpn_irb_mode": resourceSchema.StringAttribute{
MarkdownDescription: fmt.Sprintf("Symmetric IRB Routing for EVPN on Junos devices makes use of "+
"an L3 VNI for inter-subnet routing which is embedded into EVPN Type2-routes to support better "+
"scaling for networks with large amounts of VLANs. Applicable only to Apstra 4.2.0+. When omitted, "+
"Routing Zones in Apstra 4.2.0 and later will be configured with mode `%s`.", junosEvpnIrbModeDefault),
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
Validators: []validator.String{stringvalidator.OneOf(apstra.JunosEvpnIrbModes.Values()...)},
// Default: DO NOT USE stringdefault.StaticString(apstra.JunosEvpnIrbModeAsymmetric.Value) here
// because that will set the attribute for Apstra < 4.2.0 (which do not support it) leading to
// confusion.
},
}
}

Expand Down Expand Up @@ -291,12 +313,13 @@ func (o *DatacenterRoutingZone) Request(ctx context.Context, client *apstra.Clie
}

return &apstra.SecurityZoneData{
SzType: apstra.SecurityZoneTypeEVPN,
VrfName: o.Name.ValueString(),
Label: o.Name.ValueString(),
RoutingPolicyId: apstra.ObjectId(o.RoutingPolicyId.ValueString()),
VlanId: vlan,
VniId: vni,
SzType: apstra.SecurityZoneTypeEVPN,
VrfName: o.Name.ValueString(),
Label: o.Name.ValueString(),
RoutingPolicyId: apstra.ObjectId(o.RoutingPolicyId.ValueString()),
VlanId: vlan,
VniId: vni,
JunosEvpnIrbMode: apstra.JunosEvpnIrbModes.Parse(o.JunosEvpnIrbMode.ValueString()),
RtPolicy: &apstra.RtPolicy{
ImportRTs: importRTs,
ExportRTs: exportRTs,
Expand Down Expand Up @@ -341,6 +364,11 @@ func (o *DatacenterRoutingZone) LoadApiData(ctx context.Context, sz *apstra.Secu
if sz.RtPolicy != nil && sz.RtPolicy.ExportRTs != nil {
o.ExportRouteTargets = utils.SetValueOrNull(ctx, types.StringType, sz.RtPolicy.ExportRTs, diags)
}

o.JunosEvpnIrbMode = types.StringNull()
if sz.JunosEvpnIrbMode != nil {
o.JunosEvpnIrbMode = types.StringValue(sz.JunosEvpnIrbMode.Value)
}
}

func (o *DatacenterRoutingZone) LoadApiDhcpServers(ctx context.Context, IPs []net.IP, diags *diag.Diagnostics) {
Expand Down Expand Up @@ -472,5 +500,9 @@ func (o *DatacenterRoutingZone) szNodeQueryAttributes(name string) []apstra.QEEA
result = append(result, apstra.QEEAttribute{Key: "vlan_id", Value: apstra.QEIntVal(int(o.VlanId.ValueInt64()))})
}

if utils.Known(o.JunosEvpnIrbMode) {
result = append(result, apstra.QEEAttribute{Key: "junos_evpn_irb_mode", Value: apstra.QEStringVal(o.JunosEvpnIrbMode.ValueString())})
}

return result
}
4 changes: 2 additions & 2 deletions apstra/data_source_datacenter_routing_zones.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (o *dataSourceDatacenterRoutingZones) Schema(_ context.Context, _ datasourc
apstravalidator.AtLeastNAttributes(
1,
"name", "vlan_id", "vni", "dhcp_servers", "routing_policy_id",
"import_route_targets", "export_route_targets",
"import_route_targets", "export_route_targets", "junos_evpn_irb_mode",
),
},
},
Expand All @@ -78,7 +78,7 @@ func (o *dataSourceDatacenterRoutingZones) Schema(_ context.Context, _ datasourc
apstravalidator.AtLeastNAttributes(
1,
"name", "vlan_id", "vni", "dhcp_servers", "routing_policy_id",
"import_route_targets", "export_route_targets",
"import_route_targets", "export_route_targets", "junos_evpn_irb_mode",
),
},
},
Expand Down
2 changes: 1 addition & 1 deletion apstra/iba/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (o *Widget) LoadApiData(ctx context.Context, in *apstra.IbaWidget, d *diag.
o.ProbeId = types.StringValue(in.Data.ProbeId.String())
}

func (o *Widget) Request(ctx context.Context, d *diag.Diagnostics) *apstra.IbaWidgetData {
func (o *Widget) Request(_ context.Context, _ *diag.Diagnostics) *apstra.IbaWidgetData {
return &apstra.IbaWidgetData{
StageName: o.Stage.ValueString(),
Description: o.Description.ValueString(),
Expand Down
42 changes: 29 additions & 13 deletions apstra/resource_datacenter_routing_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ func (o *resourceDatacenterRoutingZone) Create(ctx context.Context, req resource
}

request := plan.Request(ctx, o.client, &resp.Diagnostics)
dhcpServerRequest := plan.DhcpServerRequest(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
Expand All @@ -146,14 +145,22 @@ func (o *resourceDatacenterRoutingZone) Create(ctx context.Context, req resource
resp.Diagnostics.AddError("error creating security zone", err.Error())
return
}

// partial state set
plan.Id = types.StringValue(id.String())
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)

err = bp.SetSecurityZoneDhcpServers(ctx, id, dhcpServerRequest)
if err != nil {
resp.Diagnostics.AddError("error setting security zone dhcp servers", err.Error())
return
if !plan.DhcpServers.IsNull() {
dhcpServerRequest := plan.DhcpServerRequest(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

err = bp.SetSecurityZoneDhcpServers(ctx, id, dhcpServerRequest)
if err != nil {
resp.Diagnostics.AddError("error setting security zone dhcp servers", err.Error())
return
}
}

sz, err := bp.GetSecurityZone(ctx, id)
Expand Down Expand Up @@ -235,6 +242,13 @@ func (o *resourceDatacenterRoutingZone) Update(ctx context.Context, req resource
return
}

// Retrieve values from state.
var state blueprint.DatacenterRoutingZone
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

// Create a blueprint client
bp, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(plan.BlueprintId.ValueString()))
if err != nil {
Expand Down Expand Up @@ -262,15 +276,17 @@ func (o *resourceDatacenterRoutingZone) Update(ctx context.Context, req resource
return
}

dhcpRequest := plan.DhcpServerRequest(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
if !plan.DhcpServers.Equal(state.DhcpServers) {
dhcpRequest := plan.DhcpServerRequest(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

err = bp.SetSecurityZoneDhcpServers(ctx, apstra.ObjectId(plan.Id.ValueString()), dhcpRequest)
if err != nil {
resp.Diagnostics.AddError("error updating security zone dhcp servers", err.Error())
return
err = bp.SetSecurityZoneDhcpServers(ctx, apstra.ObjectId(plan.Id.ValueString()), dhcpRequest)
if err != nil {
resp.Diagnostics.AddError("error updating security zone dhcp servers", err.Error())
return
}
}

sz, err := bp.GetSecurityZone(ctx, apstra.ObjectId(plan.Id.ValueString()))
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/datacenter_routing_zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ output "routing_zone" {
- `had_prior_vlan_id_config` (Boolean) Used to trigger plan modification when `vlan_id` has been removed from the configuration in managed resource context, this attribute will always be `null` and should be ignored in data source context.
- `had_prior_vni_config` (Boolean) Used to trigger plan modification when `vni` has been removed from the configuration in managed resource context, this attribute will always be `null` and should be ignored in data source context.
- `import_route_targets` (Set of String) Used to import routes into the EVPN VRF.
- `junos_evpn_irb_mode` (String) Symmetric IRB Routing for EVPN on Junos devices makes use of an L3 VNI for inter-subnet routing which is embedded into EVPN Type2-routes to support better scaling for networks with large amounts of VLANs.
- `routing_policy_id` (String) Non-EVPN blueprints must use the default policy, so this field must be null. Set this attribute in an EVPN blueprint to use a non-default policy.
- `vlan_id` (Number) Used for VLAN tagged Layer 3 links on external connections. Leave this field blank to have it automatically assigned from a static pool in the range of 2-4094), or enter a specific value.
- `vni` (Number) VxLAN VNI associated with the routing zone. Leave this field blank to have it automatically assigned from an allocated resource pool, or enter a specific value.
2 changes: 2 additions & 0 deletions docs/data-sources/datacenter_routing_zones.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Optional:
- `dhcp_servers` (Set of String) Set of addresses of DHCP servers (IPv4 or IPv6) which must be configured in the Routing Zone. This is a list of *required* servers, not an exact-match list.
- `export_route_targets` (Set of String) This is a set of *required* RTs, not an exact-match list.
- `import_route_targets` (Set of String) This is a set of *required* RTs, not an exact-match list.
- `junos_evpn_irb_mode` (String) Symmetric IRB Routing for EVPN on Junos devices makes use of an L3 VNI for inter-subnet routing which is embedded into EVPN Type2-routes to support better scaling for networks with large amounts of VLANs.
- `name` (String) VRF name displayed in the Apstra web UI.
- `routing_policy_id` (String) Non-EVPN blueprints must use the default policy, so this field must be null. Set this attribute in an EVPN blueprint to use a non-default policy.
- `vlan_id` (Number) Used for VLAN tagged Layer 3 links on external connections.
Expand All @@ -79,6 +80,7 @@ Optional:
- `dhcp_servers` (Set of String) Set of addresses of DHCP servers (IPv4 or IPv6) which must be configured in the Routing Zone. This is a list of *required* servers, not an exact-match list.
- `export_route_targets` (Set of String) This is a set of *required* RTs, not an exact-match list.
- `import_route_targets` (Set of String) This is a set of *required* RTs, not an exact-match list.
- `junos_evpn_irb_mode` (String) Symmetric IRB Routing for EVPN on Junos devices makes use of an L3 VNI for inter-subnet routing which is embedded into EVPN Type2-routes to support better scaling for networks with large amounts of VLANs.
- `name` (String) VRF name displayed in the Apstra web UI.
- `routing_policy_id` (String) Non-EVPN blueprints must use the default policy, so this field must be null. Set this attribute in an EVPN blueprint to use a non-default policy.
- `vlan_id` (Number) Used for VLAN tagged Layer 3 links on external connections.
Expand Down
1 change: 1 addition & 0 deletions docs/resources/datacenter_routing_zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ resource "apstra_datacenter_routing_zone" "blue" {
- `dhcp_servers` (Set of String) Set of DHCP server IPv4 or IPv6 addresses of DHCP servers.
- `export_route_targets` (Set of String) Used to export routes from the EVPN VRF.
- `import_route_targets` (Set of String) Used to import routes into the EVPN VRF.
- `junos_evpn_irb_mode` (String) Symmetric IRB Routing for EVPN on Junos devices makes use of an L3 VNI for inter-subnet routing which is embedded into EVPN Type2-routes to support better scaling for networks with large amounts of VLANs. Applicable only to Apstra 4.2.0+. When omitted, Routing Zones in Apstra 4.2.0 and later will be configured with mode `asymmetric`.
- `routing_policy_id` (String) Non-EVPN blueprints must use the default policy, so this field must be null. Set this attribute in an EVPN blueprint to use a non-default policy.
- `vlan_id` (Number) Used for VLAN tagged Layer 3 links on external connections. Leave this field blank to have it automatically assigned from a static pool in the range of 2-4094, or enter a specific value.
- `vni` (Number) VxLAN VNI associated with the routing zone. Leave this field blank to have it automatically assigned from an allocated resource pool, or enter a specific value.
Expand Down

0 comments on commit 953e13d

Please sign in to comment.