diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e7234220..8fa6f67197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,11 @@ All notable changes to this project will be documented in this file. - Activator - Automatically backfill flex-algo node segment IDs for all activated devices when a new `TopologyInfo` account is created onchain - Automatically backfill existing topologies' node segments when a Vpnv4 loopback interface is activated on a device +- Controller + - Add `features.yaml` config file support (`--features-config` flag); a `flex_algo.enabled` flag gates all flex-algo config generation + - When flex-algo is enabled: generate IS-IS TE admin-group assignments on link interfaces, flex-algo topology definitions, and `system-colored-tunnel-rib` as the BGP next-hop resolution source + - Stamp BGP color extcommunity on user tunnel route-maps for tenants with `include_topologies` set + - Add `node-segment ipv4 index` lines to Vpnv4 loopback config for each flex-algo, sourced from segment routing IDs backfilled by the activator - Client - Add `doublezero_connection_info` Prometheus metric exposing connection metadata (user_type, network, current_device, metro, tunnel_name, tunnel_src, tunnel_dst) ([#3201](https://github.com/malbeclabs/doublezero/pull/3201)) - Add `doublezero_connection_rtt_nanoseconds` and `doublezero_connection_loss_percentage` Prometheus metrics reporting RTT and packet loss to the current connected device diff --git a/controlplane/controller/cmd/controller/main.go b/controlplane/controller/cmd/controller/main.go index 059db3e56e..1b88aa3ad6 100644 --- a/controlplane/controller/cmd/controller/main.go +++ b/controlplane/controller/cmd/controller/main.go @@ -129,24 +129,26 @@ func NewControllerCommand() *ControllerCommand { c.fs.StringVar(&c.tlsKeyFile, "tls-key", "", "path to tls key file") c.fs.BoolVar(&c.enablePprof, "enable-pprof", false, "enable pprof server") c.fs.StringVar(&c.tlsListenPort, "tls-listen-port", "", "listening port for controller grpc server") + c.fs.StringVar(&c.featuresConfigPath, "features-config", "", "path to features.yaml config file (optional)") return c } type ControllerCommand struct { - fs *flag.FlagSet - description string - listenAddr string - listenPort string - env string - programID string - rpcEndpoint string - deviceLocalASN uint64 - noHardware bool - showVersion bool - tlsCertFile string - tlsKeyFile string - tlsListenPort string - enablePprof bool + fs *flag.FlagSet + description string + listenAddr string + listenPort string + env string + programID string + rpcEndpoint string + deviceLocalASN uint64 + noHardware bool + showVersion bool + tlsCertFile string + tlsKeyFile string + tlsListenPort string + enablePprof bool + featuresConfigPath string } func (c *ControllerCommand) Fs() *flag.FlagSet { @@ -249,6 +251,22 @@ func (c *ControllerCommand) Run() error { log.Info("clickhouse disabled (CLICKHOUSE_ADDR not set)") } + if c.featuresConfigPath != "" { + f, err := os.Open(c.featuresConfigPath) + if err != nil { + log.Error("failed to open features config", "path", c.featuresConfigPath, "error", err) + os.Exit(1) + } + featuresConfig, err := controller.LoadFeaturesConfig(f) + f.Close() + if err != nil { + log.Error("failed to parse features config", "path", c.featuresConfigPath, "error", err) + os.Exit(1) + } + options = append(options, controller.WithFeaturesConfig(featuresConfig)) + log.Info("features config loaded", "path", c.featuresConfigPath, "flex_algo_enabled", featuresConfig.Features.FlexAlgo.Enabled) + } + if c.noHardware { options = append(options, controller.WithNoHardware()) } diff --git a/controlplane/controller/internal/controller/features_config.go b/controlplane/controller/internal/controller/features_config.go new file mode 100644 index 0000000000..6dc2826e5e --- /dev/null +++ b/controlplane/controller/internal/controller/features_config.go @@ -0,0 +1,68 @@ +package controller + +import ( + "io" + "slices" + + "gopkg.in/yaml.v3" +) + +// FeaturesConfig is loaded from a YAML file at controller startup. +// It gates flex-algo topology config, link tagging, and BGP color community stamping. +type FeaturesConfig struct { + Features struct { + FlexAlgo FlexAlgoConfig `yaml:"flex_algo"` + } `yaml:"features"` +} + +// FlexAlgoConfig controls IS-IS Flex-Algo and BGP color extended community behaviour. +type FlexAlgoConfig struct { + Enabled bool `yaml:"enabled"` + LinkTagging LinkTaggingConfig `yaml:"link_tagging"` + CommunityStamping CommunityStampingConfig `yaml:"community_stamping"` +} + +// LinkTaggingConfig controls which links receive IS-IS TE admin-group attributes. +type LinkTaggingConfig struct { + Exclude struct { + Links []string `yaml:"links"` // link pubkeys to skip + } `yaml:"exclude"` +} + +// IsExcluded returns true if the given link pubkey is in the exclude list. +func (c *LinkTaggingConfig) IsExcluded(linkPubKey string) bool { + return slices.Contains(c.Exclude.Links, linkPubKey) +} + +// CommunityStampingConfig controls BGP color extended community stamping per tenant/device. +// A device is stamped if All is true, OR its pubkey is in Devices, OR the tenant's pubkey +// is in Tenants — unless the device pubkey is in Exclude.Devices (overrides all). +type CommunityStampingConfig struct { + All bool `yaml:"all"` + Tenants []string `yaml:"tenants"` // tenant pubkeys + Devices []string `yaml:"devices"` // device pubkeys + Exclude struct { + Devices []string `yaml:"devices"` + } `yaml:"exclude"` +} + +// ShouldStamp returns true if BGP color communities should be stamped for the +// given (tenantPubKey, devicePubKey) pair. +func (c *CommunityStampingConfig) ShouldStamp(tenantPubKey, devicePubKey string) bool { + if slices.Contains(c.Exclude.Devices, devicePubKey) { + return false + } + if c.All { + return true + } + return slices.Contains(c.Tenants, tenantPubKey) || slices.Contains(c.Devices, devicePubKey) +} + +// LoadFeaturesConfig parses a features YAML config from the given reader. +func LoadFeaturesConfig(r io.Reader) (*FeaturesConfig, error) { + var cfg FeaturesConfig + if err := yaml.NewDecoder(r).Decode(&cfg); err != nil { + return nil, err + } + return &cfg, nil +} diff --git a/controlplane/controller/internal/controller/features_config_test.go b/controlplane/controller/internal/controller/features_config_test.go new file mode 100644 index 0000000000..6fbbb7c05c --- /dev/null +++ b/controlplane/controller/internal/controller/features_config_test.go @@ -0,0 +1,94 @@ +package controller + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFeaturesConfigLoad(t *testing.T) { + yaml := ` +features: + flex_algo: + enabled: true + link_tagging: + exclude: + links: + - ABC123pubkey + community_stamping: + all: false + tenants: + - TenantPubkey1 + devices: + - DevicePubkey1 + exclude: + devices: + - ExcludedDevicePubkey +` + config, err := LoadFeaturesConfig(strings.NewReader(yaml)) + require.NoError(t, err) + assert.True(t, config.Features.FlexAlgo.Enabled) + assert.Len(t, config.Features.FlexAlgo.LinkTagging.Exclude.Links, 1) + assert.Equal(t, "ABC123pubkey", config.Features.FlexAlgo.LinkTagging.Exclude.Links[0]) + assert.False(t, config.Features.FlexAlgo.CommunityStamping.All) + assert.Len(t, config.Features.FlexAlgo.CommunityStamping.Tenants, 1) + assert.Len(t, config.Features.FlexAlgo.CommunityStamping.Devices, 1) + assert.Len(t, config.Features.FlexAlgo.CommunityStamping.Exclude.Devices, 1) +} + +func TestFeaturesConfigEmpty(t *testing.T) { + yaml := `features: {}` + config, err := LoadFeaturesConfig(strings.NewReader(yaml)) + require.NoError(t, err) + assert.False(t, config.Features.FlexAlgo.Enabled) + assert.Empty(t, config.Features.FlexAlgo.LinkTagging.Exclude.Links) +} + +func TestLinkTaggingIsExcluded(t *testing.T) { + cfg := LinkTaggingConfig{} + cfg.Exclude.Links = []string{"pubkey1", "pubkey2"} + assert.True(t, cfg.IsExcluded("pubkey1")) + assert.True(t, cfg.IsExcluded("pubkey2")) + assert.False(t, cfg.IsExcluded("pubkey3")) + assert.False(t, cfg.IsExcluded("")) +} + +func TestShouldStamp(t *testing.T) { + cfg := CommunityStampingConfig{ + All: false, + Tenants: []string{"tenant1"}, + Devices: []string{"device1"}, + } + cfg.Exclude.Devices = []string{"excluded_device"} + + // tenant in list, device not excluded → stamp + assert.True(t, cfg.ShouldStamp("tenant1", "any_device")) + // device in list, tenant not in list → stamp + assert.True(t, cfg.ShouldStamp("other_tenant", "device1")) + // excluded device — always false regardless of tenant/device match + assert.False(t, cfg.ShouldStamp("tenant1", "excluded_device")) + // not in any list + assert.False(t, cfg.ShouldStamp("other_tenant", "other_device")) +} + +func TestShouldStampAllTrue(t *testing.T) { + cfg := CommunityStampingConfig{All: true} + assert.True(t, cfg.ShouldStamp("any_tenant", "any_device")) + + cfg.Exclude.Devices = []string{"excluded"} + assert.False(t, cfg.ShouldStamp("any_tenant", "excluded")) + assert.True(t, cfg.ShouldStamp("any_tenant", "other_device")) +} + +func TestShouldStampExcludeOverridesAll(t *testing.T) { + cfg := CommunityStampingConfig{ + All: true, + Tenants: []string{"tenant1"}, + Devices: []string{"device1"}, + } + cfg.Exclude.Devices = []string{"device1"} + // device1 is both in Devices and Exclude.Devices — exclude wins + assert.False(t, cfg.ShouldStamp("tenant1", "device1")) +} diff --git a/controlplane/controller/internal/controller/fixtures/base.config.drained.txt b/controlplane/controller/internal/controller/fixtures/base.config.drained.txt index 068eca9658..4290b92a4e 100644 --- a/controlplane/controller/internal/controller/fixtures/base.config.drained.txt +++ b/controlplane/controller/internal/controller/fixtures/base.config.drained.txt @@ -81,6 +81,8 @@ interface Ethernet1/1 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Ethernet1/2 mtu 2048 @@ -121,6 +123,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -137,8 +140,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/base.config.flex-algo-disabled.txt b/controlplane/controller/internal/controller/fixtures/base.config.flex-algo-disabled.txt new file mode 100644 index 0000000000..e3c7889267 --- /dev/null +++ b/controlplane/controller/internal/controller/fixtures/base.config.flex-algo-disabled.txt @@ -0,0 +1,195 @@ +! +hardware counter feature gre tunnel interface out +hardware counter feature gre tunnel interface in +! +hardware access-list update default-result permit +! +logging buffered 128000 +no logging console +logging facility local7 +! +ip name-server vrf default 1.1.1.1 +ip name-server vrf default 9.9.9.9 +clock timezone UTC +! +ip multicast-routing +! +router pim sparse-mode + ipv4 + rp address 10.0.0.0 239.0.0.0/24 override +! +vrf instance vrf1 +ip routing +ip routing vrf vrf1 +! +ntp server 0.pool.ntp.org +ntp server 1.pool.ntp.org +ntp server 2.pool.ntp.org +! +hardware access-list update default-result permit +! +no ip access-list MAIN-CONTROL-PLANE-ACL +ip access-list MAIN-CONTROL-PLANE-ACL + counters per-entry + 10 permit icmp any any + 20 permit ip any any tracked + 30 permit udp any any eq bfd ttl eq 255 + 40 permit udp any any eq bfd-echo ttl eq 254 + 50 permit udp any any eq multihop-bfd micro-bfd sbfd + 60 permit udp any eq sbfd any eq sbfd-initiator + 70 permit ospf any any + 80 permit tcp any any eq ssh telnet www snmp bgp https msdp ldp netconf-ssh gnmi + 90 permit udp any any eq bootps bootpc snmp rip ntp ldp ptp-event ptp-general + 100 permit tcp any any eq mlag ttl eq 255 + 110 permit udp any any eq mlag ttl eq 255 + 120 permit vrrp any any + 130 permit ahp any any + 140 permit pim any any + 150 permit igmp any any + 160 permit tcp any any range 5900 5910 + 170 permit tcp any any range 50000 50100 + 180 permit udp any any range 51000 51100 + 190 permit tcp any any eq 3333 + 200 permit tcp any any eq nat ttl eq 255 + 210 permit tcp any eq bgp any + 220 permit rsvp any any + 230 permit tcp any any eq 9340 + 240 permit tcp any any eq 9559 + 250 permit udp any any eq 8503 + 260 permit udp any any eq lsp-ping + 270 permit udp any eq lsp-ping any + 280 remark Permit TWAMP (UDP 862) + 290 permit udp any any eq 862 +! +system control-plane + ip access-group MAIN-CONTROL-PLANE-ACL in +! +interface Ethernet1/1 + mtu 2048 + no switchport + ip address 172.16.0.2/31 + pim ipv4 sparse-mode + isis enable 1 + isis circuit-type level-2 + isis hello-interval 1 + isis metric 40000 + no isis passive + isis hello padding + isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering +! +interface Loopback1000 + description RP Address + ip address 10.0.0.0/32 +! +mpls ip +! +mpls icmp ttl-exceeded tunneling +mpls icmp ip source-interface Loopback255 +! +default interface Tunnel500 +interface Tunnel500 + description USER-UCAST-500 + ip access-group SEC-USER-500-IN in + vrf vrf1 + mtu 9216 + ip address 169.254.0.0/31 + tunnel mode gre + tunnel source 1.1.1.1 + tunnel destination 2.2.2.2 + tunnel path-mtu-discovery + tunnel ttl 32 + no shutdown +! +router bgp 65342 + router-id 14.14.14.14 + timers bgp 1 3 + distance bgp 20 200 200 + address-family ipv4 + ! + address-family vpn-ipv4 + no next-hop resolution ribs + ! + vrf vrf1 + rd 65342:1 + route-target import vpn-ipv4 65342:1 + route-target export vpn-ipv4 65342:1 + router-id 7.7.7.7 + no neighbor 169.254.0.1 + neighbor 169.254.0.1 remote-as 65000 + neighbor 169.254.0.1 local-as 65342 no-prepend replace-as + neighbor 169.254.0.1 passive + neighbor 169.254.0.1 description USER-500 + neighbor 169.254.0.1 route-map RM-USER-500-IN in + neighbor 169.254.0.1 route-map RM-USER-500-OUT out + neighbor 169.254.0.1 maximum-routes 1 + neighbor 169.254.0.1 maximum-accepted-routes 1 +! +router isis 1 + net 49.0000.0e0e.0e0e.0000.00 + router-id ipv4 14.14.14.14 + log-adjacency-changes + ! + address-family ipv4 unicast + ! + segment-routing mpls + no shutdown + no flex-algo unicast-default level-2 + no traffic-engineering + no set-overload-bit +! +no router traffic-engineering +! +ip community-list COMM-ALL_USERS permit 21682:1200 +ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 +ip community-list COMM-TST_USERS permit 21682:10050 +! +no route-map RM-USER-500-OUT +route-map RM-USER-500-OUT deny 10 + match community COMM-TST_USERS +route-map RM-USER-500-OUT permit 20 + match community COMM-ALL_USERS +! +no route-map RM-USER-500-IN +route-map RM-USER-500-IN permit 10 + match ip address prefix-list PL-USER-500 + match as-path length = 1 + set community 21682:1200 21682:10050 +! +no ip prefix-list PL-USER-500 +ip prefix-list PL-USER-500 seq 10 permit 100.0.0.0/32 +! +no ip access-list SEC-USER-500-IN +ip access-list SEC-USER-500-IN + counters per-entry + !ICMP + permit icmp 169.254.0.1/32 any + !TRACEROUTE + permit udp 169.254.0.1/32 any range 33434 33534 + !Allow TTL Exceeded for traceroute return packets + permit icmp 169.254.0.1/32 any time-exceeded + !PERMIT BGP + permit tcp 169.254.0.1/32 169.254.0.0/32 eq 179 + !PERMIT USER DZ_IP as a source only + permit ip 100.0.0.0/32 any + deny ip any any +! +no ip access-list standard SEC-USER-MCAST-BOUNDARY-500-OUT +no ip access-list SEC-USER-PUB-MCAST-IN +ip access-list SEC-USER-PUB-MCAST-IN + counters per-entry + permit icmp any any + permit tcp any any eq bgp + permit ip any 224.0.0.13/32 + permit ip any 239.0.0.0/24 + deny ip any any +! +no ip access-list SEC-USER-SUB-MCAST-IN +ip access-list SEC-USER-SUB-MCAST-IN + counters per-entry + permit icmp any any + permit tcp any any eq bgp + permit ip any 224.0.0.13/32 + deny ip any any +! diff --git a/controlplane/controller/internal/controller/fixtures/base.config.flex-algo.txt b/controlplane/controller/internal/controller/fixtures/base.config.flex-algo.txt new file mode 100644 index 0000000000..aaa93dbefe --- /dev/null +++ b/controlplane/controller/internal/controller/fixtures/base.config.flex-algo.txt @@ -0,0 +1,214 @@ +! +hardware counter feature gre tunnel interface out +hardware counter feature gre tunnel interface in +! +hardware access-list update default-result permit +! +logging buffered 128000 +no logging console +logging facility local7 +! +ip name-server vrf default 1.1.1.1 +ip name-server vrf default 9.9.9.9 +clock timezone UTC +! +ip multicast-routing +! +router pim sparse-mode + ipv4 + rp address 10.0.0.0 239.0.0.0/24 override +! +vrf instance vrf1 +ip routing +ip routing vrf vrf1 +! +ntp server 0.pool.ntp.org +ntp server 1.pool.ntp.org +ntp server 2.pool.ntp.org +! +hardware access-list update default-result permit +! +no ip access-list MAIN-CONTROL-PLANE-ACL +ip access-list MAIN-CONTROL-PLANE-ACL + counters per-entry + 10 permit icmp any any + 20 permit ip any any tracked + 30 permit udp any any eq bfd ttl eq 255 + 40 permit udp any any eq bfd-echo ttl eq 254 + 50 permit udp any any eq multihop-bfd micro-bfd sbfd + 60 permit udp any eq sbfd any eq sbfd-initiator + 70 permit ospf any any + 80 permit tcp any any eq ssh telnet www snmp bgp https msdp ldp netconf-ssh gnmi + 90 permit udp any any eq bootps bootpc snmp rip ntp ldp ptp-event ptp-general + 100 permit tcp any any eq mlag ttl eq 255 + 110 permit udp any any eq mlag ttl eq 255 + 120 permit vrrp any any + 130 permit ahp any any + 140 permit pim any any + 150 permit igmp any any + 160 permit tcp any any range 5900 5910 + 170 permit tcp any any range 50000 50100 + 180 permit udp any any range 51000 51100 + 190 permit tcp any any eq 3333 + 200 permit tcp any any eq nat ttl eq 255 + 210 permit tcp any eq bgp any + 220 permit rsvp any any + 230 permit tcp any any eq 9340 + 240 permit tcp any any eq 9559 + 250 permit udp any any eq 8503 + 260 permit udp any any eq lsp-ping + 270 permit udp any eq lsp-ping any + 280 remark Permit TWAMP (UDP 862) + 290 permit udp any any eq 862 +! +system control-plane + ip access-group MAIN-CONTROL-PLANE-ACL in +! +interface Ethernet1/1 + mtu 2048 + no switchport + ip address 172.16.0.2/31 + pim ipv4 sparse-mode + isis enable 1 + isis circuit-type level-2 + isis hello-interval 1 + isis metric 40000 + no isis passive + isis hello padding + isis network point-to-point + traffic-engineering + traffic-engineering administrative-group UNICAST-DEFAULT +! +interface Loopback255 + ip address 14.14.14.14/32 + node-segment ipv4 index 100 + node-segment ipv4 index 200 flex-algo unicast-default + isis enable 1 +! +interface Loopback1000 + description RP Address + ip address 10.0.0.0/32 +! +mpls ip +! +mpls icmp ttl-exceeded tunneling +mpls icmp ip source-interface Loopback255 +! +default interface Tunnel500 +interface Tunnel500 + description USER-UCAST-500 + ip access-group SEC-USER-500-IN in + vrf vrf1 + mtu 9216 + ip address 169.254.0.0/31 + tunnel mode gre + tunnel source 1.1.1.1 + tunnel destination 2.2.2.2 + tunnel path-mtu-discovery + tunnel ttl 32 + no shutdown +! +router bgp 65342 + router-id 14.14.14.14 + timers bgp 1 3 + distance bgp 20 200 200 + address-family ipv4 + ! + address-family vpn-ipv4 + next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + ! + vrf vrf1 + rd 65342:1 + route-target import vpn-ipv4 65342:1 + route-target export vpn-ipv4 65342:1 + router-id 7.7.7.7 + no neighbor 169.254.0.1 + neighbor 169.254.0.1 remote-as 65000 + neighbor 169.254.0.1 local-as 65342 no-prepend replace-as + neighbor 169.254.0.1 passive + neighbor 169.254.0.1 description USER-500 + neighbor 169.254.0.1 route-map RM-USER-500-IN in + neighbor 169.254.0.1 route-map RM-USER-500-OUT out + neighbor 169.254.0.1 maximum-routes 1 + neighbor 169.254.0.1 maximum-accepted-routes 1 +! +router isis 1 + net 49.0000.0e0e.0e0e.0000.00 + router-id ipv4 14.14.14.14 + log-adjacency-changes + ! + address-family ipv4 unicast + ! + segment-routing mpls + no shutdown + flex-algo unicast-default level-2 advertised + traffic-engineering + no shutdown + is-type level-2 + no set-overload-bit +! +router traffic-engineering + router-id ipv4 14.14.14.14 + segment-routing + rib system-colored-tunnel-rib + administrative-group alias UNICAST-DRAINED group 0 + administrative-group alias UNICAST-DEFAULT group 1 + flex-algo + flex-algo 129 unicast-default + administrative-group include any 1 exclude 0 + color 2 + ! +! +ip community-list COMM-ALL_USERS permit 21682:1200 +ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 +ip community-list COMM-TST_USERS permit 21682:10050 +! +no route-map RM-USER-500-OUT +route-map RM-USER-500-OUT deny 10 + match community COMM-TST_USERS +route-map RM-USER-500-OUT permit 20 + match community COMM-ALL_USERS +! +no route-map RM-USER-500-IN +route-map RM-USER-500-IN permit 10 + match ip address prefix-list PL-USER-500 + match as-path length = 1 + set community 21682:1200 21682:10050 + set extcommunity color 2 +! +no ip prefix-list PL-USER-500 +ip prefix-list PL-USER-500 seq 10 permit 100.0.0.0/32 +! +no ip access-list SEC-USER-500-IN +ip access-list SEC-USER-500-IN + counters per-entry + !ICMP + permit icmp 169.254.0.1/32 any + !TRACEROUTE + permit udp 169.254.0.1/32 any range 33434 33534 + !Allow TTL Exceeded for traceroute return packets + permit icmp 169.254.0.1/32 any time-exceeded + !PERMIT BGP + permit tcp 169.254.0.1/32 169.254.0.0/32 eq 179 + !PERMIT USER DZ_IP as a source only + permit ip 100.0.0.0/32 any + deny ip any any +! +no ip access-list standard SEC-USER-MCAST-BOUNDARY-500-OUT +no ip access-list SEC-USER-PUB-MCAST-IN +ip access-list SEC-USER-PUB-MCAST-IN + counters per-entry + permit icmp any any + permit tcp any any eq bgp + permit ip any 224.0.0.13/32 + permit ip any 239.0.0.0/24 + deny ip any any +! +no ip access-list SEC-USER-SUB-MCAST-IN +ip access-list SEC-USER-SUB-MCAST-IN + counters per-entry + permit icmp any any + permit tcp any any eq bgp + permit ip any 224.0.0.13/32 + deny ip any any +! diff --git a/controlplane/controller/internal/controller/fixtures/base.config.txt b/controlplane/controller/internal/controller/fixtures/base.config.txt index 0319bb29fd..3ae3fb78cf 100644 --- a/controlplane/controller/internal/controller/fixtures/base.config.txt +++ b/controlplane/controller/internal/controller/fixtures/base.config.txt @@ -81,6 +81,8 @@ interface Ethernet1/1 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Ethernet1/2 mtu 2048 @@ -121,6 +123,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -137,8 +140,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/base.config.with.mgmt.vrf.txt b/controlplane/controller/internal/controller/fixtures/base.config.with.mgmt.vrf.txt index 0302495de7..fae2013870 100644 --- a/controlplane/controller/internal/controller/fixtures/base.config.with.mgmt.vrf.txt +++ b/controlplane/controller/internal/controller/fixtures/base.config.with.mgmt.vrf.txt @@ -97,6 +97,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -113,8 +114,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/base.config.without.interfaces.peers.txt b/controlplane/controller/internal/controller/fixtures/base.config.without.interfaces.peers.txt index 68f15a7c6c..703bb4a800 100644 --- a/controlplane/controller/internal/controller/fixtures/base.config.without.interfaces.peers.txt +++ b/controlplane/controller/internal/controller/fixtures/base.config.without.interfaces.peers.txt @@ -77,6 +77,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 diff --git a/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl b/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl index 90fda914ee..5fac0001e1 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl @@ -96,6 +96,7 @@ router bgp 65342 address-family vpn-ipv4 no neighbor 172.16.0.1 no neighbor 169.254.0.13 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -114,8 +115,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/e2e.multi.vrf.tmpl b/controlplane/controller/internal/controller/fixtures/e2e.multi.vrf.tmpl index 03feccf5b6..db27913a5c 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.multi.vrf.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.multi.vrf.tmpl @@ -122,6 +122,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -161,8 +162,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/e2e.peer.removal.tmpl b/controlplane/controller/internal/controller/fixtures/e2e.peer.removal.tmpl index 1404986549..627625f547 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.peer.removal.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.peer.removal.tmpl @@ -136,6 +136,7 @@ router bgp 65342 address-family vpn-ipv4 no neighbor 172.16.0.1 no neighbor 169.254.0.7 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -163,8 +164,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/e2e.tmpl b/controlplane/controller/internal/controller/fixtures/e2e.tmpl index 3fe69a8585..71094438b7 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.tmpl @@ -85,6 +85,8 @@ interface Switch1/1/1 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Switch1/1/2 mtu 2048 @@ -102,6 +104,8 @@ interface Switch1/1/2.100 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Switch1/1/3 mtu 2048 @@ -115,6 +119,8 @@ interface Switch1/1/3 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Loopback1000 description RP Address @@ -175,6 +181,7 @@ router bgp 65342 neighbor 169.254.0.3 activate ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -200,8 +207,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/e2e.without.interfaces.peers.tmpl b/controlplane/controller/internal/controller/fixtures/e2e.without.interfaces.peers.tmpl index dbc38d819f..bf5d82f886 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.without.interfaces.peers.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.without.interfaces.peers.tmpl @@ -77,6 +77,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 diff --git a/controlplane/controller/internal/controller/fixtures/interfaces.txt b/controlplane/controller/internal/controller/fixtures/interfaces.txt index 27a3c7c8c7..80de582f3e 100644 --- a/controlplane/controller/internal/controller/fixtures/interfaces.txt +++ b/controlplane/controller/internal/controller/fixtures/interfaces.txt @@ -85,6 +85,8 @@ interface Switch1/1/1 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Switch1/1/2 mtu 2048 @@ -114,6 +116,8 @@ interface Switch1/1/2.300 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Vlan4001 mtu 2048 @@ -126,6 +130,8 @@ interface Vlan4001 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Switch1/1/3 mtu 2048 @@ -139,6 +145,8 @@ interface Switch1/1/3 no isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Switch1/1/4 mtu 2048 @@ -152,6 +160,8 @@ interface Switch1/1/4 isis passive isis hello padding isis network point-to-point + no traffic-engineering administrative-group + no traffic-engineering ! interface Switch1/1/5 mtu 2048 @@ -179,6 +189,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -195,8 +206,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/metro.routing.disabled.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/metro.routing.disabled.tunnel.tmpl index a7b66890eb..b6441c8b7f 100644 --- a/controlplane/controller/internal/controller/fixtures/metro.routing.disabled.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/metro.routing.disabled.tunnel.tmpl @@ -108,6 +108,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -142,8 +143,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/mixed.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/mixed.tunnel.tmpl index f98c7a3d88..dd5c7cc0c8 100644 --- a/controlplane/controller/internal/controller/fixtures/mixed.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/mixed.tunnel.tmpl @@ -174,6 +174,7 @@ router bgp 65342 neighbor 169.254.0.7 activate ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -199,8 +200,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl index 1f6f3e38a0..f52749d670 100644 --- a/controlplane/controller/internal/controller/fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl @@ -110,6 +110,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -149,8 +150,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/multi.vrf.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/multi.vrf.tunnel.tmpl index 3ccdc582ed..a4dbee85d2 100644 --- a/controlplane/controller/internal/controller/fixtures/multi.vrf.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/multi.vrf.tunnel.tmpl @@ -110,6 +110,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -149,8 +150,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/multicast.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/multicast.tunnel.tmpl index 356902ad9d..4429dc88e4 100644 --- a/controlplane/controller/internal/controller/fixtures/multicast.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/multicast.tunnel.tmpl @@ -160,6 +160,7 @@ router bgp 65342 neighbor 169.254.0.5 activate ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -176,8 +177,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/nohardware.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/nohardware.tunnel.tmpl index b07a53e73a..057ed11d4d 100644 --- a/controlplane/controller/internal/controller/fixtures/nohardware.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/nohardware.tunnel.tmpl @@ -163,6 +163,7 @@ router bgp 65342 neighbor 169.254.0.7 activate ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -188,8 +189,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/unicast.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/unicast.tunnel.tmpl index d849c63eb4..5e15764866 100644 --- a/controlplane/controller/internal/controller/fixtures/unicast.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/unicast.tunnel.tmpl @@ -122,6 +122,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -165,8 +166,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/fixtures/unknown.peer.removal.tmpl b/controlplane/controller/internal/controller/fixtures/unknown.peer.removal.tmpl index 442313f074..7a6ad7f9e5 100644 --- a/controlplane/controller/internal/controller/fixtures/unknown.peer.removal.tmpl +++ b/controlplane/controller/internal/controller/fixtures/unknown.peer.removal.tmpl @@ -124,6 +124,7 @@ router bgp 65342 ! address-family vpn-ipv4 no neighbor 169.254.0.7 + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -168,8 +169,11 @@ router isis 1 ! segment-routing mpls no shutdown + no traffic-engineering no set-overload-bit ! +no router traffic-engineering +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-TST_USERS permit 21682:10050 diff --git a/controlplane/controller/internal/controller/models.go b/controlplane/controller/internal/controller/models.go index dcf970c675..87813b792b 100644 --- a/controlplane/controller/internal/controller/models.go +++ b/controlplane/controller/internal/controller/models.go @@ -45,11 +45,17 @@ type Interface struct { LinkStatus serviceability.LinkStatus IsCYOA bool IsDIA bool + // RFC-18: set when the interface is an activated link with topology assignments + LinkTopologies []string // topology names (resolved from link.link_topologies pubkeys) + UnicastDrained bool + PubKey string // base58-encoded link pubkey (set when IsLink is true) + // RFC-18: flex-algo node-segment data for VPNv4 loopback interfaces + FlexAlgoNodeSegments []FlexAlgoNodeSegmentModel } // toInterface validates onchain data for a serviceability interface and converts it to a controller interface. func toInterface(iface serviceability.Interface) (Interface, error) { - if iface.IpNet == ([5]byte{}) && iface.Name == "" { + if iface.Name == "" { return Interface{}, errors.New("serviceability interface cannot be nil") } @@ -242,6 +248,9 @@ type Tunnel struct { MulticastBoundaryList []net.IP MulticastSubscribers []net.IP MulticastPublishers []net.IP + // RFC-18: tenant identification and topology color stamping + TenantPubKey string + TenantTopologyColors string // e.g. "color 1" or "color 1 color 3", empty if flex-algo disabled } // bgpMartianNets contains the standard BGP martian prefixes — addresses that @@ -292,6 +301,18 @@ func (StringsHelper) ToUpper(s string) string { return strings.ToUpper(s) } +func (StringsHelper) Join(sep string, parts []string) string { + return strings.Join(parts, sep) +} + +func (StringsHelper) ToUpperEach(parts []string) []string { + result := make([]string, len(parts)) + for i, s := range parts { + result[i] = strings.ToUpper(s) + } + return result +} + type templateData struct { Device *Device Vpnv4BgpPeers []BgpPeer @@ -303,4 +324,26 @@ type templateData struct { LocalASN uint32 UnicastVrfs []uint16 Strings StringsHelper + AllTopologies []TopologyModel + Config *FeaturesConfig // nil when no features config is loaded +} + +// FlexAlgoEnabled returns true if a features config is loaded and flex_algo.enabled is set. +func (d templateData) FlexAlgoEnabled() bool { + return d.Config != nil && d.Config.Features.FlexAlgo.Enabled +} + +// TopologyModel holds pre-computed topology data for template rendering. +type TopologyModel struct { + Name string + AdminGroupBit uint8 + FlexAlgoNumber uint8 + Color int // AdminGroupBit + 1 + ConstraintStr string // "include-any" or "exclude" +} + +// FlexAlgoNodeSegmentModel holds pre-computed flex-algo node-segment data for template rendering. +type FlexAlgoNodeSegmentModel struct { + NodeSegmentIdx uint16 + TopologyName string } diff --git a/controlplane/controller/internal/controller/render_test.go b/controlplane/controller/internal/controller/render_test.go index 6d5b2b31aa..56da619495 100644 --- a/controlplane/controller/internal/controller/render_test.go +++ b/controlplane/controller/internal/controller/render_test.go @@ -797,6 +797,136 @@ func TestRenderConfig(t *testing.T) { }, Want: "fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl", }, + { + Name: "render_flex_algo_enabled_successfully", + Description: "render config with flex-algo enabled: interface admin-groups, node-segments, router TE block, BGP color stamping", + Data: templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: func() *FeaturesConfig { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.CommunityStamping.All = true + return cfg + }(), + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 1, + FlexAlgoNumber: 129, + Color: 2, + ConstraintStr: "include-any", + }, + }, + Device: &Device{ + PubKey: "4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM", + PublicIP: net.IP{7, 7, 7, 7}, + Vpn4vLoopbackIP: net.IP{14, 14, 14, 14}, + Vpn4vLoopbackIntfName: "Loopback255", + IsisNet: "49.0000.0e0e.0e0e.0000.00", + ExchangeCode: "tst", + BgpCommunity: 10050, + Interfaces: []Interface{ + { + Name: "Ethernet1/1", + Ip: netip.MustParsePrefix("172.16.0.2/31"), + Mtu: 2048, + InterfaceType: InterfaceTypePhysical, + Metric: 40000, + IsLink: true, + LinkStatus: serviceability.LinkStatusActivated, + LinkTopologies: []string{"unicast-default"}, + }, + { + Name: "Loopback255", + Ip: netip.MustParsePrefix("14.14.14.14/32"), + NodeSegmentIdx: 100, + InterfaceType: InterfaceTypeLoopback, + LoopbackType: LoopbackTypeVpnv4, + FlexAlgoNodeSegments: []FlexAlgoNodeSegmentModel{ + {NodeSegmentIdx: 200, TopologyName: "unicast-default"}, + }, + }, + }, + Tunnels: []*Tunnel{ + { + Id: 500, + UnderlaySrcIP: net.IP{1, 1, 1, 1}, + UnderlayDstIP: net.IP{2, 2, 2, 2}, + OverlaySrcIP: net.IP{169, 254, 0, 0}, + OverlayDstIP: net.IP{169, 254, 0, 1}, + DzIp: net.IP{100, 0, 0, 0}, + Allocated: true, + VrfId: 1, + MetroRouting: true, + TenantPubKey: "g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT", + TenantTopologyColors: "color 2", + }, + }, + }, + }, + Want: "fixtures/base.config.flex-algo.txt", + }, + { + Name: "render_flex_algo_disabled_cleanup_successfully", + Description: "render config with flex-algo disabled but topologies present: cleanup commands emitted, no TE config", + Data: templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: nil, + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 0, + FlexAlgoNumber: 128, + Color: 1, + ConstraintStr: "include-any", + }, + }, + Device: &Device{ + PublicIP: net.IP{7, 7, 7, 7}, + Vpn4vLoopbackIP: net.IP{14, 14, 14, 14}, + Vpn4vLoopbackIntfName: "Loopback255", + IsisNet: "49.0000.0e0e.0e0e.0000.00", + ExchangeCode: "tst", + BgpCommunity: 10050, + Interfaces: []Interface{ + { + Name: "Ethernet1/1", + Ip: netip.MustParsePrefix("172.16.0.2/31"), + Mtu: 2048, + InterfaceType: InterfaceTypePhysical, + Metric: 40000, + IsLink: true, + LinkStatus: serviceability.LinkStatusActivated, + LinkTopologies: []string{"unicast-default"}, + }, + }, + Tunnels: []*Tunnel{ + { + Id: 500, + UnderlaySrcIP: net.IP{1, 1, 1, 1}, + UnderlayDstIP: net.IP{2, 2, 2, 2}, + OverlaySrcIP: net.IP{169, 254, 0, 0}, + OverlayDstIP: net.IP{169, 254, 0, 1}, + DzIp: net.IP{100, 0, 0, 0}, + Allocated: true, + VrfId: 1, + MetroRouting: true, + TenantPubKey: "g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT", + TenantTopologyColors: "color 1", + }, + }, + }, + }, + Want: "fixtures/base.config.flex-algo-disabled.txt", + }, } for _, test := range tests { diff --git a/controlplane/controller/internal/controller/server.go b/controlplane/controller/internal/controller/server.go index 37b51569b2..cada842f36 100644 --- a/controlplane/controller/internal/controller/server.go +++ b/controlplane/controller/internal/controller/server.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "sort" + "strings" "sync" "time" @@ -53,6 +54,7 @@ type stateCache struct { Devices map[string]*Device MulticastGroups map[string]serviceability.MulticastGroup Tenants map[string]serviceability.Tenant + Topologies map[string]serviceability.TopologyInfo // keyed by base58 pubkey UnicastVrfs []uint16 Vpnv4BgpPeers []BgpPeer Ipv4BgpPeers []BgpPeer @@ -72,6 +74,7 @@ type Controller struct { environment string deviceLocalASN uint32 clickhouse *ClickhouseWriter + featuresConfig *FeaturesConfig } type Option func(*Controller) @@ -160,6 +163,13 @@ func WithClickhouse(cw *ClickhouseWriter) Option { } } +// WithFeaturesConfig provides a loaded FeaturesConfig to the controller. +func WithFeaturesConfig(cfg *FeaturesConfig) Option { + return func(c *Controller) { + c.featuresConfig = cfg + } +} + // processDeviceInterfacesAndPeers processes a device's interfaces and extracts BGP peer information. // It returns the candidate VPNv4 and IPv4 BGP peers found from the device's loopback interfaces. func (c *Controller) processDeviceInterfacesAndPeers(device serviceability.Device, d *Device, devicePubKey string) (candidateVpnv4BgpPeer, candidateIpv4BgpPeer BgpPeer) { @@ -260,6 +270,11 @@ func (c *Controller) updateStateCache(ctx context.Context) error { Config: data.Config, Devices: make(map[string]*Device), MulticastGroups: make(map[string]serviceability.MulticastGroup), + Topologies: make(map[string]serviceability.TopologyInfo), + } + + for _, t := range data.Topologies { + cache.Topologies[base58.Encode(t.PubKey[:])] = t } // TODO: valid interface checks: @@ -348,22 +363,22 @@ func (c *Controller) updateStateCache(ctx context.Context) error { cache.Ipv4BgpPeers = append(cache.Ipv4BgpPeers, candidateIpv4BgpPeer) // determine if interface is in an onchain link and assign metrics - findLink := func(intf Interface) *serviceability.Link { - for i, link := range links { + findLink := func(intf Interface) (serviceability.Link, bool) { + for _, link := range links { if d.PubKey == base58.Encode(link.SideAPubKey[:]) && intf.Name == link.SideAIfaceName { - return &links[i] + return link, true } if d.PubKey == base58.Encode(link.SideZPubKey[:]) && intf.Name == link.SideZIfaceName { - return &links[i] + return link, true } } - return nil + return serviceability.Link{}, false } for i, iface := range d.Interfaces { - link := findLink(iface) + link, linkFound := findLink(iface) - if link == nil || (link.Status != serviceability.LinkStatusActivated && link.Status != serviceability.LinkStatusSoftDrained && link.Status != serviceability.LinkStatusHardDrained) { + if !linkFound || (link.Status != serviceability.LinkStatusActivated && link.Status != serviceability.LinkStatusSoftDrained && link.Status != serviceability.LinkStatusHardDrained) { d.Interfaces[i].IsLink = false d.Interfaces[i].Metric = 0 d.Interfaces[i].LinkStatus = serviceability.LinkStatusPending @@ -397,9 +412,43 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } d.Interfaces[i].IsLink = true d.Interfaces[i].LinkStatus = link.Status + d.Interfaces[i].UnicastDrained = link.LinkFlags&0x01 != 0 + d.Interfaces[i].PubKey = base58.Encode(link.PubKey[:]) + + // Resolve topology names from link_topologies pubkeys + for _, topoKey := range link.LinkTopologies { + pk := base58.Encode(topoKey[:]) + if topo, ok := cache.Topologies[pk]; ok { + d.Interfaces[i].LinkTopologies = append(d.Interfaces[i].LinkTopologies, topo.Name) + } + } + linkMetrics.WithLabelValues(device.Code, iface.Name, d.PubKey).Set(float64(d.Interfaces[i].Metric)) } + // Populate flex-algo node-segment data for VPNv4 loopback interfaces + if c.featuresConfig != nil && c.featuresConfig.Features.FlexAlgo.Enabled { + for i, intf := range d.Interfaces { + if !intf.IsVpnv4Loopback() { + continue + } + for _, onchainIface := range device.Interfaces { + if onchainIface.Name == intf.Name { + for _, seg := range onchainIface.FlexAlgoNodeSegments { + pk := base58.Encode(seg.Topology[:]) + if topo, ok := cache.Topologies[pk]; ok { + d.Interfaces[i].FlexAlgoNodeSegments = append(d.Interfaces[i].FlexAlgoNodeSegments, FlexAlgoNodeSegmentModel{ + NodeSegmentIdx: seg.NodeSegmentIdx, + TopologyName: topo.Name, + }) + } + } + break + } + } + } + } + cache.Devices[devicePubKey] = d } @@ -512,6 +561,8 @@ func (c *Controller) updateStateCache(ctx context.Context) error { tunnel.PubKey = userPubKey tunnel.Allocated = true + tunnel.TenantPubKey = base58.Encode(user.TenantPubKey[:]) + if user.UserType != serviceability.UserTypeMulticast { // Set the VRF ID and metro routing from the user's tenant. Default to VRF 1 with metro routing enabled. tunnel.VrfId = 1 @@ -519,6 +570,10 @@ func (c *Controller) updateStateCache(ctx context.Context) error { if tenant, ok := cache.Tenants[base58.Encode(user.TenantPubKey[:])]; ok { tunnel.VrfId = tenant.VrfId tunnel.MetroRouting = tenant.MetroRouting + + if c.featuresConfig != nil && c.featuresConfig.Features.FlexAlgo.Enabled { + tunnel.TenantTopologyColors = resolveTenantColors(tenant.IncludeTopologies, cache.Topologies) + } } } @@ -556,6 +611,22 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } } + // Check for VPNv4 loopbacks missing flex-algo node-segment data when flex-algo is enabled + if c.featuresConfig != nil && c.featuresConfig.Features.FlexAlgo.Enabled && len(cache.Topologies) > 0 { + for devicePubKey, d := range cache.Devices { + if len(d.DevicePathologies) > 0 { + continue + } + for _, intf := range d.Interfaces { + if intf.IsVpnv4Loopback() && len(intf.FlexAlgoNodeSegments) == 0 { + c.log.Error("flex_algo.enabled=true but VPNv4 loopback has no flex_algo_node_segments — run 'doublezero-admin migrate' and restart", + "device_pubkey", devicePubKey, + "interface", intf.Name) + } + } + } + } + // swap out state cache with new version c.log.Debug("updating state cache", "state cache", cache) c.swapCache(cache) @@ -600,6 +671,13 @@ func (c *Controller) Run(ctx context.Context) error { c.log.Error("error fetching accounts", "error", err) } cacheUpdateOps.Inc() + if c.featuresConfig != nil && c.featuresConfig.Features.FlexAlgo.Enabled { + c.mu.RLock() + if len(c.cache.Topologies) == 0 { + c.log.Warn("flex_algo.enabled=true but no topology accounts found on-chain — verify doublezero-admin migrate has been run") + } + c.mu.RUnlock() + } ticker := time.NewTicker(10 * time.Second) for { select { @@ -694,6 +772,29 @@ func (c *Controller) deduplicateTunnels(device *Device) []*Tunnel { return unique } +// resolveTenantColors computes the "color N color M ..." string for a tenant's include_topologies. +// If include_topologies is empty, uses UNICAST-DEFAULT (the first topology with name "unicast-default"). +// Returns empty string if no topologies can be resolved. +func resolveTenantColors(includeTopologies [][32]byte, topologyMap map[string]serviceability.TopologyInfo) string { + var colors []string + if len(includeTopologies) > 0 { + for _, pk := range includeTopologies { + if topo, ok := topologyMap[base58.Encode(pk[:])]; ok { + colors = append(colors, fmt.Sprintf("color %d", int(topo.AdminGroupBit)+1)) + } + } + } else { + // default: use unicast-default topology + for _, topo := range topologyMap { + if topo.Name == "unicast-default" { + colors = append(colors, fmt.Sprintf("color %d", int(topo.AdminGroupBit)+1)) + break + } + } + } + return strings.Join(colors, " ") +} + // GetConfig renders the latest device configuration based on cached device data func (c *Controller) GetConfig(ctx context.Context, req *pb.ConfigRequest) (*pb.ConfigResponse, error) { reqStart := time.Now() @@ -797,6 +898,20 @@ func (c *Controller) GetConfig(ctx context.Context, req *pb.ConfigRequest) (*pb. return nil, err } + var allTopologies []TopologyModel + for _, topo := range c.cache.Topologies { + allTopologies = append(allTopologies, TopologyModel{ + Name: topo.Name, + AdminGroupBit: topo.AdminGroupBit, + FlexAlgoNumber: topo.FlexAlgoNumber, + Color: int(topo.AdminGroupBit) + 1, + ConstraintStr: topo.Constraint.String(), + }) + } + sort.Slice(allTopologies, func(i, j int) bool { + return allTopologies[i].AdminGroupBit < allTopologies[j].AdminGroupBit + }) + data := templateData{ MulticastGroupBlock: multicastGroupBlock, Device: &deviceForRender, @@ -808,6 +923,8 @@ func (c *Controller) GetConfig(ctx context.Context, req *pb.ConfigRequest) (*pb. LocalASN: localASN, UnicastVrfs: c.cache.UnicastVrfs, Strings: StringsHelper{}, + AllTopologies: allTopologies, + Config: c.featuresConfig, } config, err := renderConfig(data) diff --git a/controlplane/controller/internal/controller/server_test.go b/controlplane/controller/internal/controller/server_test.go index 9d3feffd1f..51d8acfebc 100644 --- a/controlplane/controller/internal/controller/server_test.go +++ b/controlplane/controller/internal/controller/server_test.go @@ -924,7 +924,8 @@ func TestStateCache(t *testing.T) { MulticastIp: [4]uint8{239, 0, 0, 1}, }, }, - Tenants: map[string]serviceability.Tenant{}, + Tenants: map[string]serviceability.Tenant{}, + Topologies: map[string]serviceability.TopologyInfo{}, UnicastVrfs: []uint16{1}, Vpnv4BgpPeers: []BgpPeer{ { @@ -960,6 +961,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "11111111111111111111111111111111", }, { Id: 501, @@ -971,6 +973,7 @@ func TestStateCache(t *testing.T) { PubKey: "11111111111111111111111111111111", Allocated: true, IsMulticast: true, + TenantPubKey: "11111111111111111111111111111111", MulticastBoundaryList: []net.IP{ {239, 0, 0, 1}, }, @@ -989,6 +992,7 @@ func TestStateCache(t *testing.T) { IsLink: true, Metric: 400000, LinkStatus: serviceability.LinkStatusActivated, + PubKey: "11111111111111111111111111111111", }, { InterfaceType: InterfaceTypePhysical, @@ -998,6 +1002,7 @@ func TestStateCache(t *testing.T) { IsLink: true, Metric: 1, LinkStatus: serviceability.LinkStatusActivated, + PubKey: "11111111111111111111111111111111", }, { InterfaceType: InterfaceTypePhysical, @@ -1007,6 +1012,7 @@ func TestStateCache(t *testing.T) { IsLink: true, Metric: 50, LinkStatus: serviceability.LinkStatusActivated, + PubKey: "11111111111111111111111111111111", }, { InterfaceType: InterfaceTypeLoopback, @@ -1077,6 +1083,7 @@ func TestStateCache(t *testing.T) { }, MulticastGroups: map[string]serviceability.MulticastGroup{}, Tenants: map[string]serviceability.Tenant{}, + Topologies: map[string]serviceability.TopologyInfo{}, UnicastVrfs: []uint16{1}, Vpnv4BgpPeers: nil, // No BGP peers since device has pathologies Devices: map[string]*Device{ @@ -1106,6 +1113,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "11111111111111111111111111111111", }, }, generateEmptyTunnelSlots(config.StartUserTunnelNum+1, config.MaxUserTunnelSlots-1)...), TunnelSlots: config.MaxUserTunnelSlots, @@ -1173,6 +1181,7 @@ func TestStateCache(t *testing.T) { }, MulticastGroups: map[string]serviceability.MulticastGroup{}, Tenants: map[string]serviceability.Tenant{}, + Topologies: map[string]serviceability.TopologyInfo{}, UnicastVrfs: []uint16{1}, Devices: map[string]*Device{ "4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM": { @@ -1218,6 +1227,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "11111111111111111111111111111111", }, }, generateEmptyTunnelSlots(config.StartUserTunnelNum+1, config.MaxUserTunnelSlots-1)...), TunnelSlots: config.MaxUserTunnelSlots, @@ -1299,6 +1309,7 @@ func TestStateCache(t *testing.T) { }, MulticastGroups: map[string]serviceability.MulticastGroup{}, Tenants: map[string]serviceability.Tenant{}, + Topologies: map[string]serviceability.TopologyInfo{}, UnicastVrfs: []uint16{1}, Vpnv4BgpPeers: []BgpPeer{ { @@ -1354,6 +1365,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "11111111111111111111111111111111", }, { Id: 501, @@ -1366,6 +1378,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "11111111111111111111111111111111", }, }, generateEmptyTunnelSlots(config.StartUserTunnelNum+2, config.MaxUserTunnelSlots-2)...), TunnelSlots: config.MaxUserTunnelSlots, @@ -1479,6 +1492,7 @@ func TestStateCache(t *testing.T) { VrfId: 2, }, }, + Topologies: map[string]serviceability.TopologyInfo{}, UnicastVrfs: []uint16{1, 2}, Vpnv4BgpPeers: []BgpPeer{ { @@ -1513,6 +1527,7 @@ func TestStateCache(t *testing.T) { PubKey: "11111111111111111111111111111111", Allocated: true, VrfId: 1, + TenantPubKey: "g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT", }, { Id: 501, @@ -1524,6 +1539,7 @@ func TestStateCache(t *testing.T) { PubKey: "11111111111111111111111111111111", Allocated: true, VrfId: 2, + TenantPubKey: "2M59vuWgsiuHAqQVB6KvuXuaBCJR8138gMAm4uCuR6Du", }, { Id: 502, @@ -1536,6 +1552,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "7fTN12qMUn1gSUuTMxNCdjndcxwJu45kosXuqJiXMeT9", }, }, generateEmptyTunnelSlots(config.StartUserTunnelNum+3, config.MaxUserTunnelSlots-3)...), TunnelSlots: config.MaxUserTunnelSlots, diff --git a/controlplane/controller/internal/controller/templates/tunnel.tmpl b/controlplane/controller/internal/controller/templates/tunnel.tmpl index 840b764016..5df6c3cced 100644 --- a/controlplane/controller/internal/controller/templates/tunnel.tmpl +++ b/controlplane/controller/internal/controller/templates/tunnel.tmpl @@ -107,6 +107,15 @@ interface {{ .Name }} {{- end }} {{- if and .IsVpnv4Loopback .NodeSegmentIdx}} node-segment ipv4 index {{ .NodeSegmentIdx }} + {{- if $.FlexAlgoEnabled }} + {{- range .FlexAlgoNodeSegments }} + node-segment ipv4 index {{ .NodeSegmentIdx }} flex-algo {{ .TopologyName }} + {{- end }} + {{- else }} + {{- range .FlexAlgoNodeSegments }} + no node-segment ipv4 index {{ .NodeSegmentIdx }} flex-algo {{ .TopologyName }} + {{- end }} + {{- end }} {{- end }} {{- if and .Ip.IsValid .IsLoopback }} isis enable 1 @@ -123,6 +132,21 @@ interface {{ .Name }} {{- end }} isis hello padding isis network point-to-point + {{- if and .Ip.IsValid .IsPhysical .Metric .IsLink (not .IsSubInterfaceParent) (not .IsCYOA) (not .IsDIA) }} + {{- if $.FlexAlgoEnabled }} + traffic-engineering + {{- if and .LinkTopologies (not ($.Config.Features.FlexAlgo.LinkTagging.IsExcluded .PubKey)) }} + traffic-engineering administrative-group {{ $.Strings.Join " " ($.Strings.ToUpperEach .LinkTopologies) }}{{ if .UnicastDrained }} UNICAST-DRAINED{{ end }} + {{- else if .UnicastDrained }} + traffic-engineering administrative-group UNICAST-DRAINED + {{- else }} + no traffic-engineering administrative-group + {{- end }} + {{- else }} + no traffic-engineering administrative-group + no traffic-engineering + {{- end }} + {{- end }} {{- end }} ! {{- end }} @@ -247,6 +271,11 @@ router bgp 65342 {{- range .UnknownBgpPeers }} no neighbor {{ . }} {{- end }} + {{- if $.FlexAlgoEnabled }} + next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + {{- else }} + no next-hop resolution ribs + {{- end }} ! {{- range $vrfId := .UnicastVrfs }} vrf vrf{{ $vrfId }} @@ -284,12 +313,50 @@ router isis 1 ! segment-routing mpls no shutdown + {{- if $.FlexAlgoEnabled }} + {{- range $.AllTopologies }} + flex-algo {{ .Name }} level-2 advertised + {{- end }} + {{- else }} + {{- range $.AllTopologies }} + no flex-algo {{ .Name }} level-2 + {{- end }} + {{- end }} + {{- if $.FlexAlgoEnabled }} + traffic-engineering + no shutdown + is-type level-2 + {{- else }} + no traffic-engineering + {{- end }} {{- if $.Device.Status.IsDrained }} set-overload-bit {{- else }} no set-overload-bit {{- end }} ! +{{- if $.FlexAlgoEnabled }} +router traffic-engineering + router-id ipv4 {{ $.Device.Vpn4vLoopbackIP }} + segment-routing + rib system-colored-tunnel-rib + administrative-group alias UNICAST-DRAINED group 0 + {{- range $.AllTopologies }} + administrative-group alias {{ $.Strings.ToUpper .Name }} group {{ .AdminGroupBit }} + {{- end }} + flex-algo + {{- range $.AllTopologies }} + {{- if eq .ConstraintStr "include-any" }} + flex-algo {{ .FlexAlgoNumber }} {{ .Name }} + administrative-group include any {{ .AdminGroupBit }} exclude 0 + color {{ .Color }} + ! + {{- end }} + {{- end }} +{{- else }} +no router traffic-engineering +{{- end }} +! ip community-list COMM-ALL_USERS permit 21682:1200 ip community-list COMM-ALL_MCAST_USERS permit 21682:1300 ip community-list COMM-{{ .Strings.ToUpper .Device.ExchangeCode }}_USERS permit 21682:{{ .Device.BgpCommunity }} @@ -313,6 +380,11 @@ route-map RM-USER-{{ .Id }}-IN permit 10 match ip address prefix-list PL-USER-{{ .Id }} match as-path length = 1 set community 21682:{{ if eq true .IsMulticast }}1300{{ else }}1200{{ end }} 21682:{{ $.Device.BgpCommunity }} + {{- if and $.FlexAlgoEnabled (not .IsMulticast) .TenantTopologyColors }} + {{- if $.Config.Features.FlexAlgo.CommunityStamping.ShouldStamp .TenantPubKey $.Device.PubKey }} + set extcommunity {{ .TenantTopologyColors }} + {{- end }} + {{- end }} {{- end }} ! {{- end }} diff --git a/smartcontract/sdk/go/serviceability/client.go b/smartcontract/sdk/go/serviceability/client.go index 17ccd44cb2..f7bfc4aeac 100644 --- a/smartcontract/sdk/go/serviceability/client.go +++ b/smartcontract/sdk/go/serviceability/client.go @@ -25,6 +25,7 @@ type ProgramData struct { ProgramConfig ProgramConfig ResourceExtensions []ResourceExtension Permissions []Permission + Topologies []TopologyInfo } func New(rpc RPCClient, programID solana.PublicKey) *Client { @@ -59,6 +60,7 @@ func (c *Client) GetProgramData(ctx context.Context) (*ProgramData, error) { programConfig := ProgramConfig{} resourceExtensions := []ResourceExtension{} permissions := []Permission{} + topologies := []TopologyInfo{} var errs error for _, element := range out { @@ -125,6 +127,11 @@ func (c *Client) GetProgramData(ctx context.Context) (*ProgramData, error) { DeserializePermission(reader, &permission) permission.PubKey = element.Pubkey permissions = append(permissions, permission) + case byte(TopologyType): + var topology TopologyInfo + DeserializeTopologyInfo(reader, &topology) + topology.PubKey = element.Pubkey + topologies = append(topologies, topology) } } @@ -141,6 +148,7 @@ func (c *Client) GetProgramData(ctx context.Context) (*ProgramData, error) { ProgramConfig: programConfig, ResourceExtensions: resourceExtensions, Permissions: permissions, + Topologies: topologies, }, errs } diff --git a/smartcontract/sdk/go/serviceability/client_test.go b/smartcontract/sdk/go/serviceability/client_test.go index 18c18851af..6fd01d4596 100644 --- a/smartcontract/sdk/go/serviceability/client_test.go +++ b/smartcontract/sdk/go/serviceability/client_test.go @@ -59,7 +59,7 @@ f8198607689246e25c9403fba46e89122ff5d0fcc1febb51d4b 00007479322d647a30313a6c61322d647a30310001020304050 60708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 0b000000737769746368312f312f31030000006c6f30ffc99a3 -b00000000ad2570a0cf27761cab55a3f26d85fb20 +b0000000000000000000000 ` var userPayload = ` @@ -199,6 +199,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -231,6 +232,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -294,6 +296,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -327,6 +330,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -363,6 +367,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -391,9 +396,6 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ContributorPubKey: [32]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}, SideAIfaceName: "switch1/1/1", SideZIfaceName: "lo0", - LinkHealth: LinkHealth(173), - LinkDesiredStatus: LinkDesiredStatus(37), - LinkFlags: 118, LinkTopologies: nil, PubKey: pubkeys[5], }, @@ -408,6 +410,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -441,6 +444,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -479,6 +483,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -505,6 +510,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { }, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, }