diff --git a/Makefile b/Makefile index 40efa08..2223eb7 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ HOSTNAME=registry.terraform.io NAMESPACE=netrisai NAME=netris BINARY=terraform-provider-${NAME} -VERSION=3.4.1 +VERSION=3.5.0 OS_ARCH=darwin_arm64 WORKDIRECTORY=examples diff --git a/docs/resources/bgp.md b/docs/resources/bgp.md index 73e6d51..6a8b75e 100644 --- a/docs/resources/bgp.md +++ b/docs/resources/bgp.md @@ -58,7 +58,7 @@ resource "netris_bgp" "my-bgp" { - **multihop.updatesource** (String) When Multihop BGP peering is used it allows the operator to choose one of the loopback IP addresses of the SoftGate node as a BGP speaker source IP address. - **multihop.hops** (Number) Multihop BGP hops count. - **neighboras** (Number) BGP neighbor AS number. -- **outboundroutemap** (String) Reference to route-map resource ID. +- **outboundroutemap** (Number) Reference to route-map resource ID. - **portid** (Number) Port ID where BGP neighbor cable is connected. Can't be used together `vnetid`. - **prefixinboundmax** (String) BGP session will be interrupted if neighbor advertises more prefixes than defined. Equal to `1000` if BGP session is terminated on hardware type of switch. - **prefixlistinbound** (List of String) List of inbound prefix list. Example `["deny 127.0.0.0/8 le 32", "permit 0.0.0.0/0 le 24"]`. @@ -71,3 +71,7 @@ resource "netris_bgp" "my-bgp" { - **vnetid** (Number) Existing VNet service ID to terminate E-BGP on. Can't be used together `portid`. - **weight** (Number) BGP session weight. Default value is `0`. - **vpcid** (Number) ID of VPC. If not specified, the BGP will be created in the VPC marked as a default. +- **hellotimer** (Number) Hello timer is the frequency (seconds) of sending `Hello` messages. Default value is `3`. +- **holdtimer** (Number) Hold timer is the amount of time in seconds to keep BGP session up after the last received `Hello` message. This value must be at least 3 times bigger than `Hello` timer. Default value is `10`. +- **connecttimer** (Number) Connect timer is the amount of time in seconds which BGP waits between connection attempts to a neighbor. Default value is `10`. +- **bfd** (String) Valid value is `enabled` or `disabled`. Default value is `disabled`. diff --git a/docs/resources/lag.md b/docs/resources/lag.md index d1f24a2..9c42824 100644 --- a/docs/resources/lag.md +++ b/docs/resources/lag.md @@ -52,3 +52,4 @@ resource "netris_lag" "lag1_switch-01" { - **mtu** (Number) MTU must be integer between 68 and 9216. Default value is `9000` - **lacp** (String) Configuring Link Aggregation Control Protocol (LACP) signaling for the current LAG Network Interface. Valid value is `on` or `off`. The default value is `off`. - **extension** (Map of String) LAG Network Interface extension configurations +- **mclagid** (Number) Each MC-LAG requires an ID value in the range of `1-65535`, unique for the given switch-pair. diff --git a/docs/resources/link.md b/docs/resources/link.md index 8a2f706..dd11ca4 100644 --- a/docs/resources/link.md +++ b/docs/resources/link.md @@ -28,6 +28,10 @@ resource "netris_link" "sg_to_sw" { # "fc00::c82a:75ff:fe66:84b0/127", # "fc00::c82a:75ff:fe66:84b1/127" # ] + # mclag { + # sharedipv4addr = "198.51.100.50" + # anycastmacaddr = "44:38:39:ff:00:f0" + # } depends_on = [netris_softgate.my-softgate, netris_switch.my-switch] } ``` @@ -46,3 +50,13 @@ resource "netris_link" "sg_to_sw" { - **ipv4** (List of String) List of two IPv4 addresses. - **ipv6** (List of String) List of two IPv6 addresses. + +- **mclag** (Block List) Block of MC-LAG. When specified, the link is marked for MC-LAG peer link. Multiple MC-LAG peer links between the same pair of switches must have the same MC-LAG IPv4 and MAC addresses. (see [below for nested schema](#nestedblock--mclag)) + + +### Nested Schema for `mclag` + +Required: +- **sharedipv4addr** (String) MC-LAG shared IPV4 address. Shall be part of any IPAM defined subnet with the purpose set to loopback + +- **anycastmacaddr** (String) MC-LAG anycast MAC address. Recommended range 44:38:39:ff:00:00 - 44:38:39:ff:ff:ff diff --git a/examples/bgp_example.tf b/examples/bgp_example.tf index 78734d3..680bac2 100644 --- a/examples/bgp_example.tf +++ b/examples/bgp_example.tf @@ -20,18 +20,22 @@ data "netris_network_interface" "swp13_sw2" { resource "netris_bgp" "my-bgp-isp1" { - name = "my-bgp-isp1" - siteid = netris_site.santa-clara.id - hardware = "my-softgate01" - neighboras = 23456 - portid = data.netris_network_interface.swp14_sw1.id - vlanid = 3000 - localip = "172.19.25.2/30" - remoteip = "172.19.25.1/30" - description = "My ISP1 BGP" - inboundroutemap = netris_routemap.routemap-in.id - outboundroutemap = netris_routemap.routemap-out.id - localasn = "4294967295" + name = "my-bgp-isp1" + siteid = netris_site.santa-clara.id + # bfd = "enabled" + hardware = "my-softgate01" + neighboras = 23456 + portid = data.netris_network_interface.swp14_sw1.id + vlanid = 3000 + localip = "172.19.25.2/30" + remoteip = "172.19.25.1/30" + description = "My ISP1 BGP" + # inboundroutemap = netris_routemap.routemap-in.id + # outboundroutemap = netris_routemap.routemap-out.id + localasn = "4294967295" + hellotimer = 4 + holdtimer = 12 + connecttimer = 12 # state = "enabled" # multihop = { # neighboraddress = "185.54.21.5" @@ -52,36 +56,36 @@ resource "netris_bgp" "my-bgp-isp1" { depends_on = [netris_softgate.my-softgate01, netris_link.sg1_to_sw1] } -resource "netris_bgp" "my-bgp-isp2" { - name = "my-bgp-isp2" - siteid = netris_site.santa-clara.id - hardware = "my-softgate02" - neighboras = 64600 - portid = data.netris_network_interface.swp14_sw2.id - localip = "172.19.35.2/30" - remoteip = "172.19.35.1/30" - description = "My ISP2 BGP" - # inboundroutemap = netris_routemap.routemap-in.id - # outboundroutemap = netris_routemap.routemap-out.id - # state = "enabled" - # multihop = { - # neighboraddress = "185.54.21.5" - # updatesource = "198.51.100.11/32" - # hops = "5" - # } - # bgppassword = "somestrongpass" - # allowasin = 5 - # defaultoriginate = false - # prefixinboundmax = 1000 - # localpreference = 100 - # weight = 0 - # prependinbound = 2 - prependoutbound = 2 - prefixlistinbound = ["deny 127.0.0.0/8 le 32", "permit 0.0.0.0/0 le 24"] - prefixlistoutbound = ["permit 192.0.2.0/24", "permit 198.51.100.0/24 le 25", "permit 203.0.113.0/24 le 26"] - # sendbgpcommunity = ["65501:777"] - depends_on = [netris_softgate.my-softgate02, netris_link.sg2_to_sw2] -} +# resource "netris_bgp" "my-bgp-isp2" { +# name = "my-bgp-isp2" +# siteid = netris_site.santa-clara.id +# hardware = "my-softgate02" +# neighboras = 64600 +# portid = data.netris_network_interface.swp14_sw2.id +# localip = "172.19.35.2/30" +# remoteip = "172.19.35.1/30" +# description = "My ISP2 BGP" +# # inboundroutemap = netris_routemap.routemap-in.id +# # outboundroutemap = netris_routemap.routemap-out.id +# # state = "enabled" +# # multihop = { +# # neighboraddress = "185.54.21.5" +# # updatesource = "198.51.100.11/32" +# # hops = "5" +# # } +# # bgppassword = "somestrongpass" +# # allowasin = 5 +# # defaultoriginate = false +# # prefixinboundmax = 1000 +# # localpreference = 100 +# # weight = 0 +# # prependinbound = 2 +# prependoutbound = 2 +# prefixlistinbound = ["deny 127.0.0.0/8 le 32", "permit 0.0.0.0/0 le 24"] +# prefixlistoutbound = ["permit 192.0.2.0/24", "permit 198.51.100.0/24 le 25", "permit 203.0.113.0/24 le 26"] +# # sendbgpcommunity = ["65501:777"] +# depends_on = [netris_softgate.my-softgate02, netris_link.sg2_to_sw2] +# } resource "netris_bgp" "my-bgp-isp1-in-my-vpc" { @@ -117,34 +121,34 @@ resource "netris_bgp" "my-bgp-isp1-in-my-vpc" { depends_on = [netris_softgate.my-softgate01, netris_link.sg1_to_sw1] } -resource "netris_bgp" "my-bgp-isp2-in-my-vpc" { - name = "my-bgp-isp2-in-my-vpc" - siteid = netris_site.santa-clara.id - hardware = "my-softgate02" - neighboras = 64600 - portid = data.netris_network_interface.swp13_sw2.id - localip = "172.19.35.2/30" - remoteip = "172.19.35.1/30" - description = "My ISP2 BGP" - vpcid = netris_vpc.my-vpc.id - # inboundroutemap = netris_routemap.routemap-in.id - # outboundroutemap = netris_routemap.routemap-out.id - # state = "enabled" - # multihop = { - # neighboraddress = "185.54.21.5" - # updatesource = "198.51.100.11/32" - # hops = "5" - # } - # bgppassword = "somestrongpass" - # allowasin = 5 - # defaultoriginate = false - # prefixinboundmax = 1000 - # localpreference = 100 - # weight = 0 - # prependinbound = 2 - prependoutbound = 2 - prefixlistinbound = ["deny 127.0.0.0/8 le 32", "permit 0.0.0.0/0 le 24"] - prefixlistoutbound = ["permit 192.0.2.0/24", "permit 198.51.100.0/24 le 25", "permit 203.0.113.0/24 le 26"] - # sendbgpcommunity = ["65501:777"] - depends_on = [netris_softgate.my-softgate02, netris_link.sg2_to_sw2] -} +# resource "netris_bgp" "my-bgp-isp2-in-my-vpc" { +# name = "my-bgp-isp2-in-my-vpc" +# siteid = netris_site.santa-clara.id +# hardware = "my-softgate02" +# neighboras = 64600 +# portid = data.netris_network_interface.swp13_sw2.id +# localip = "172.19.35.2/30" +# remoteip = "172.19.35.1/30" +# description = "My ISP2 BGP" +# vpcid = netris_vpc.my-vpc.id +# # inboundroutemap = netris_routemap.routemap-in.id +# # outboundroutemap = netris_routemap.routemap-out.id +# # state = "enabled" +# # multihop = { +# # neighboraddress = "185.54.21.5" +# # updatesource = "198.51.100.11/32" +# # hops = "5" +# # } +# # bgppassword = "somestrongpass" +# # allowasin = 5 +# # defaultoriginate = false +# # prefixinboundmax = 1000 +# # localpreference = 100 +# # weight = 0 +# # prependinbound = 2 +# prependoutbound = 2 +# prefixlistinbound = ["deny 127.0.0.0/8 le 32", "permit 0.0.0.0/0 le 24"] +# prefixlistoutbound = ["permit 192.0.2.0/24", "permit 198.51.100.0/24 le 25", "permit 203.0.113.0/24 le 26"] +# # sendbgpcommunity = ["65501:777"] +# depends_on = [netris_softgate.my-softgate02, netris_link.sg2_to_sw2] +# } diff --git a/examples/lag_example.tf b/examples/lag_example.tf index 322a6f9..a6bdf1d 100644 --- a/examples/lag_example.tf +++ b/examples/lag_example.tf @@ -15,3 +15,24 @@ resource "netris_lag" "lag1-switch-01" { netris_switch.my-switch01, ] } + +resource "netris_lag" "lag2-mc" { + description = "my mc-lag" + tenantid = data.netris_tenant.admin.id + mtu = 9008 + # lacp = "on" + # extension = { + # extensionname = "ext1" + # vlanrange = "10-20" + # } + mclagid = 10 + members = [ + "swp10@my-switch01", + "swp10@my-switch02", + ] + depends_on = [ + netris_switch.my-switch01, + netris_switch.my-switch02, + netris_link.sw1_to_sw2_mc + ] +} diff --git a/examples/link_example.tf b/examples/link_example.tf index 1297674..7317507 100644 --- a/examples/link_example.tf +++ b/examples/link_example.tf @@ -85,3 +85,16 @@ resource "netris_link" "srv2_to_sw2" { # ] depends_on = [netris_switch.my-switch02, netris_server.my-server02] } + + +resource "netris_link" "sw1_to_sw2_mc" { + ports = [ + "swp7@my-switch01", + "swp7@my-switch02" + ] + mclag { + sharedipv4addr = "198.51.100.50" + anycastmacaddr = "44:38:39:ff:00:f0" + } + depends_on = [netris_switch.my-switch01, netris_switch.my-switch02] +} diff --git a/go.mod b/go.mod index 71edbc2..254414a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/hashicorp/terraform-plugin-sdk v1.17.2 - github.com/netrisai/netriswebapi v0.0.0-20240829195911-a229c1ed9c3d + github.com/netrisai/netriswebapi v0.0.0-20240911011151-253cf19362ec ) diff --git a/go.sum b/go.sum index 1778cfe..09a687a 100644 --- a/go.sum +++ b/go.sum @@ -400,6 +400,26 @@ github.com/netrisai/netriswebapi v0.0.0-20240829015755-e99db53a40ef h1:PcFJ0n8O0 github.com/netrisai/netriswebapi v0.0.0-20240829015755-e99db53a40ef/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= github.com/netrisai/netriswebapi v0.0.0-20240829195911-a229c1ed9c3d h1:oioJ5wHmRe5k7O+I6Wi9H59VCtAMvV/t6SBqP0OKY38= github.com/netrisai/netriswebapi v0.0.0-20240829195911-a229c1ed9c3d/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240909232127-74a86d170b1c h1:QHajZtqvIi8pDHmQqUNnoG0MI0sAxjvsojbxFO36eNA= +github.com/netrisai/netriswebapi v0.0.0-20240909232127-74a86d170b1c/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240910003135-71e4eae006f7 h1:v9XciDZUh6T+G25yTcaC6qr5GksGtXIQsxLoffmI1c4= +github.com/netrisai/netriswebapi v0.0.0-20240910003135-71e4eae006f7/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240910003745-397ddc45a432 h1:hi6XY7jWVwmBDr52keITSHgnAF7AoHJFpxxaAZtEg6A= +github.com/netrisai/netriswebapi v0.0.0-20240910003745-397ddc45a432/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240910180536-2ccefe784368 h1:3jZux+FACSzKQItHl4wRDqgcJg9Lm4L0QaivJrDNVs4= +github.com/netrisai/netriswebapi v0.0.0-20240910180536-2ccefe784368/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240910202445-971897c1c6e9 h1:NfqKm/S8gAeSdhNSBeZD+c+NSQyskOY4GIzkpNTiqhI= +github.com/netrisai/netriswebapi v0.0.0-20240910202445-971897c1c6e9/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240910210619-0bd000b915ca h1:G11axnF3C5eDemmq3maCteH9GvTa8raWNU5/eDHYFMo= +github.com/netrisai/netriswebapi v0.0.0-20240910210619-0bd000b915ca/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240910211905-828527b092ad h1:cE0xXo5UoKnz7QfuEUyoSVl5EbjK9J7C68eIw+uxJ54= +github.com/netrisai/netriswebapi v0.0.0-20240910211905-828527b092ad/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240911002837-71d243c561dc h1:aG+G42hZwIfU09MYn1zWE7pTnGb2r0nhMHpFnYNNwiQ= +github.com/netrisai/netriswebapi v0.0.0-20240911002837-71d243c561dc/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240911005910-231574b8b167 h1:YUAPJejTvkppDl3YnKLfPVKH+4OKC4gW+R6SAeHmaX0= +github.com/netrisai/netriswebapi v0.0.0-20240911005910-231574b8b167/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= +github.com/netrisai/netriswebapi v0.0.0-20240911011151-253cf19362ec h1:DR66G/+tuf5WYJfitT911HgfVQvcTUWTFxam7vQ8buA= +github.com/netrisai/netriswebapi v0.0.0-20240911011151-253cf19362ec/go.mod h1:GLLz33Jc07/hIPwEYZDWEtNtHjX/QZjVzf9xLnfSiqs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= diff --git a/netris/bgp/bgp.go b/netris/bgp/bgp.go index 2c58b67..1bf29f8 100644 --- a/netris/bgp/bgp.go +++ b/netris/bgp/bgp.go @@ -40,6 +40,13 @@ func Resource() *schema.Resource { Required: true, Description: "User assigned name of BGP session.", }, + "bfd": { + Optional: true, + Default: "disabled", + ValidateFunc: validateState, + Type: schema.TypeString, + Description: "Valid value is `enabled` or `disabled`. Default value is `disabled`.", + }, "siteid": { Required: true, Type: schema.TypeInt, @@ -110,6 +117,24 @@ func Resource() *schema.Resource { ValidateFunc: validateMultihop, }, }, + "hellotimer": { + Type: schema.TypeInt, + Optional: true, + Description: "Hello timer is the frequency (seconds) of sending `Hello` messages. Default value is `3`.", + Default: 3, + }, + "holdtimer": { + Type: schema.TypeInt, + Optional: true, + Description: "Hold timer is the amount of time in seconds to keep BGP session up after the last received `Hello` message. This value must be at least 3 times bigger than `Hello` timer. Default value is `10`.", + Default: 10, + }, + "connecttimer": { + Type: schema.TypeInt, + Optional: true, + Description: "Connect timer is the amount of time in seconds which BGP waits between connection attempts to a neighbor. Default value is `10`.", + Default: 10, + }, "bgppassword": { Optional: true, Type: schema.TypeString, @@ -133,13 +158,11 @@ func Resource() *schema.Resource { Description: "BGP session will be interrupted if neighbor advertises more prefixes than defined. Equal to `1000` if BGP session is terminated on hardware type of switch.", }, "inboundroutemap": { - Default: 0, Optional: true, Type: schema.TypeInt, Description: "Reference to route-map resource. Valid value is route-map name.", }, "outboundroutemap": { - Default: 0, Optional: true, Type: schema.TypeInt, Description: "Reference to route-map resource. Valid value is route-map name.", @@ -333,12 +356,19 @@ func resourceCreate(d *schema.ResourceData, m interface{}) error { hwIDNone = "auto" } + bgpTimers := bgp.Timers{ + Hello: d.Get("hellotimer").(int), + Hold: d.Get("holdtimer").(int), + Connect: d.Get("connecttimer").(int), + } + bgpAdd := &bgp.EBGPAdd{ Name: d.Get("name").(string), Site: bgp.IDName{ID: siteID}, Vlan: vlanID, AllowAsIn: d.Get("allowasin").(int), BgpPassword: d.Get("bgppassword").(string), + Bfd: d.Get("bfd").(string), BgpCommunity: strings.Join(communityArr, "\n"), Description: d.Get("description").(string), LocalAsn: d.Get("localasn").(string), @@ -352,8 +382,6 @@ func resourceCreate(d *schema.ResourceData, m interface{}) error { NeighborAS: d.Get("neighboras").(int), PrefixLength: prefixLength, DefaultOriginate: originate, - InboundRouteMap: d.Get("inboundroutemap").(int), - OutboundRouteMap: d.Get("outboundroutemap").(int), PrefixInboundMax: d.Get("prefixinboundmax").(string), PrefixListInbound: strings.Join(prefixListInboundArr, "\n"), PrefixListOutbound: strings.Join(prefixListOutbound, "\n"), @@ -366,12 +394,23 @@ func resourceCreate(d *schema.ResourceData, m interface{}) error { Weight: d.Get("weight").(int), Tags: []string{}, Untagged: untagged, + Timers: bgpTimers, } if vpcid > 0 { bgpAdd.Vpc = &bgp.IDName{ID: vpcid} } + iRouteMap := d.Get("inboundroutemap").(int) + oRouteMap := d.Get("outboundroutemap").(int) + if iRouteMap > 0 { + bgpAdd.InboundRouteMap = &iRouteMap + } + + if oRouteMap > 0 { + bgpAdd.OutboundRouteMap = &oRouteMap + } + js, _ := json.Marshal(bgpAdd) log.Println("[DEBUG] bgpAdd", string(js)) @@ -475,6 +514,11 @@ func resourceRead(d *schema.ResourceData, m interface{}) error { } } + err = d.Set("bfd", bgp.Bfd) + if err != nil { + return err + } + err = d.Set("localip", fmt.Sprintf("%s/%d", bgp.LocalIP, bgp.PrefixLength)) if err != nil { return err @@ -507,14 +551,31 @@ func resourceRead(d *schema.ResourceData, m interface{}) error { } } + err = d.Set("hellotimer", bgp.Timers.Hello) + if err != nil { + return err + } + + err = d.Set("holdtimer", bgp.Timers.Hold) + if err != nil { + return err + } + + err = d.Set("connecttimer", bgp.Timers.Connect) + if err != nil { + return err + } + err = d.Set("bgppassword", bgp.BgpPassword) if err != nil { return err } + err = d.Set("allowasin", bgp.AllowasIn) if err != nil { return err } + var defaultOriginate bool if bgp.DefaultOriginate == "enabled" { defaultOriginate = true @@ -694,11 +755,18 @@ func resourceUpdate(d *schema.ResourceData, m interface{}) error { bgpID, _ := strconv.Atoi(d.Id()) + bgpTimers := bgp.Timers{ + Hello: d.Get("hellotimer").(int), + Hold: d.Get("holdtimer").(int), + Connect: d.Get("connecttimer").(int), + } + bgpUpdate := &bgp.EBGPUpdate{ Name: d.Get("name").(string), Site: bgp.IDName{ID: siteID}, Vlan: vlanID, AllowAsIn: d.Get("allowasin").(int), + Bfd: d.Get("bfd").(string), BgpPassword: d.Get("bgppassword").(string), BgpCommunity: strings.Join(communityArr, "\n"), Description: d.Get("description").(string), @@ -713,8 +781,6 @@ func resourceUpdate(d *schema.ResourceData, m interface{}) error { NeighborAS: d.Get("neighboras").(int), PrefixLength: prefixLength, DefaultOriginate: originate, - InboundRouteMap: d.Get("inboundroutemap").(int), - OutboundRouteMap: d.Get("outboundroutemap").(int), PrefixInboundMax: d.Get("prefixinboundmax").(string), PrefixListInbound: strings.Join(prefixListInboundArr, "\n"), PrefixListOutbound: strings.Join(prefixListOutbound, "\n"), @@ -727,6 +793,17 @@ func resourceUpdate(d *schema.ResourceData, m interface{}) error { Weight: d.Get("weight").(int), Tags: []string{}, Untagged: untagged, + Timers: bgpTimers, + } + + iRouteMap := d.Get("inboundroutemap").(int) + oRouteMap := d.Get("outboundroutemap").(int) + if iRouteMap > 0 { + bgpUpdate.InboundRouteMap = &iRouteMap + } + + if oRouteMap > 0 { + bgpUpdate.OutboundRouteMap = &oRouteMap } js, _ := json.Marshal(bgpUpdate) diff --git a/netris/lag/lag.go b/netris/lag/lag.go index 08a9b96..886a087 100644 --- a/netris/lag/lag.go +++ b/netris/lag/lag.go @@ -93,6 +93,11 @@ func Resource() *schema.Resource { }, }, }, + "mclagid": { + Optional: true, + Type: schema.TypeInt, + Description: "Each MC-LAG requires an ID value in the range of `1-65535`, unique for the given switch-pair", + }, }, Create: resourceCreate, Read: resourceRead, @@ -124,6 +129,7 @@ func resourceCreate(d *schema.ResourceData, m interface{}) error { lagAdd.Ports = ports mtu := d.Get("mtu").(int) + mclagid := d.Get("mclagid").(int) lacp := d.Get("lacp").(string) autoneg := d.Get("autoneg").(string) if autoneg == "default" { @@ -154,6 +160,7 @@ func resourceCreate(d *schema.ResourceData, m interface{}) error { lagAdd.Mtu = mtu lagAdd.Extension = extension lagAdd.LACP = lacp + lagAdd.MCLagId = &mclagid js, _ := json.Marshal(lagAdd) log.Println("[DEBUG]", string(js)) @@ -221,6 +228,11 @@ func resourceRead(d *schema.ResourceData, m interface{}) error { return err } + err = d.Set("mclagid", hwPort.MCLagId) + if err != nil { + return err + } + var ext *port.PortLAGExtension list, err := clientset.Port().GetExtenstion() if err != nil { @@ -283,6 +295,7 @@ func resourceUpdate(d *schema.ResourceData, m interface{}) error { lagAdd.Ports = ports mtu := d.Get("mtu").(int) + mclagid := d.Get("mclagid").(int) lacp := d.Get("lacp").(string) autoneg := d.Get("autoneg").(string) if autoneg == "default" { @@ -311,6 +324,7 @@ func resourceUpdate(d *schema.ResourceData, m interface{}) error { } lagAdd.Mtu = mtu + lagAdd.MCLagId = &mclagid lagAdd.Extension = extension lagAdd.LACP = lacp diff --git a/netris/link/link.go b/netris/link/link.go index 32bec55..d2dcc85 100644 --- a/netris/link/link.go +++ b/netris/link/link.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "log" + "regexp" "strconv" "github.com/netrisai/netriswebapi/http" @@ -60,6 +61,30 @@ func Resource() *schema.Resource { }, Description: "List of two IPv6 addresses", }, + "mclag": { + ForceNew: true, + Optional: true, + Type: schema.TypeSet, + Description: "When specified, the link is marked for MC-LAG peer link. Multiple MC-LAG peer links between the same pair of switches must have the same MC-LAG IPv4 and MAC addresses.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "sharedipv4addr": { + ValidateFunc: validateIP, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "MC-LAG shared IPV4 address. Shall be part of any IPAM defined subnet with the purpose set to loopback.", + }, + "anycastmacaddr": { + ValidateFunc: validateMAC, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "MC-LAG anycast MAC address. Recommended range 44:38:39:ff:00:00 - 44:38:39:ff:ff:ff", + }, + }, + }, + }, }, Create: resourceCreate, Delete: resourceDelete, @@ -103,11 +128,39 @@ func resourceCreate(d *schema.ResourceData, m interface{}) error { remoteIpv6 = ipv6List[1].(string) } + mcLagList := d.Get("mclag").(*schema.Set).List() + linkAdd := &link.Linkw{ Local: link.LinkIDName{Name: local, Ipv4: localIpv4, Ipv6: localIpv6}, Remote: link.LinkIDName{Name: remote, Ipv4: remoteIpv4, Ipv6: remoteIpv6}, } + if len(mcLagList) > 0 { + if len(mcLagList) > 1 { + return fmt.Errorf("please specify only one mclag block") + } + + mcLAG := mcLagList[0].(map[string]interface{}) + + // Check for existence and validity of keys + sharedIPv4, ok := mcLAG["sharedipv4addr"].(string) + if !ok || sharedIPv4 == "" { + return fmt.Errorf("invalid or missing 'sharedipv4addr' in mclag block") + } + + anycastMAC, ok := mcLAG["anycastmacaddr"].(string) + if !ok || anycastMAC == "" { + return fmt.Errorf("invalid or missing 'anycastmacaddr' in mclag block") + } + + nMCLAG := link.MCLagPeerLink{ + SharedIPv4Addr: sharedIPv4, + AnycastMACAddr: anycastMAC, + } + + linkAdd.MCLagPeerLink = &nMCLAG + } + js, _ := json.Marshal(linkAdd) log.Println("[DEBUG] linkAdd", string(js)) @@ -153,20 +206,31 @@ func resourceRead(d *schema.ResourceData, m interface{}) error { portList := []interface{}{} id, _ := strconv.Atoi(d.Id()) - link, err := clientset.Link().GetByID(id) + nlink, err := clientset.Link().GetByID(id) if err != nil { return err } - portList = append(portList, link.Local.Name) - portList = append(portList, link.Remote.Name) + portList = append(portList, nlink.Local.Name) + portList = append(portList, nlink.Remote.Name) - d.SetId(strconv.Itoa(link.ID)) + d.SetId(strconv.Itoa(nlink.ID)) err = d.Set("ports", portList) if err != nil { return err } + if nlink.MCLagPeerLink != nil { + mclink := []map[string]interface{}{} + mcLag := make(map[string]interface{}) + mcLag["sharedipv4addr"] = nlink.MCLagPeerLink.SharedIPv4Addr + mcLag["anycastmacaddr"] = nlink.MCLagPeerLink.AnycastMACAddr + mclink = append(mclink, mcLag) + err = d.Set("mclag", mclink) + if err != nil { + return err + } + } return nil } @@ -216,3 +280,31 @@ func resourceImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceDa d.SetId(strconv.Itoa(link.ID)) return []*schema.ResourceData{d}, nil } + +func validateIP(val interface{}, key string) (warns []string, errs []error) { + v := val.(string) + if !validateIPAddr(v) { + errs = append(errs, fmt.Errorf("invalid %s: %s", key, v)) + } + return warns, errs +} + +func validateIPAddr(s string) bool { + re := regexp.MustCompile(`(^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12]\d|3[0-2]))?$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/([0-9]|[1-5][0-9]|6[0-4]))?$)`) + return re.Match([]byte(s)) +} + +// validateMAC validates a MAC address format +func validateMAC(val interface{}, key string) (warns []string, errs []error) { + // Convert the input value to a string + v := val.(string) + + // Regular expression pattern for validating MAC address + re := regexp.MustCompile(`^([0-9A-Fa-f]{2}([-:])){5}([0-9A-Fa-f]{2})$`) + + // Check if the MAC address matches the pattern + if !re.MatchString(v) { + errs = append(errs, fmt.Errorf("invalid %s: %s", key, v)) + } + return warns, errs +}