From 4b19b2276ce914a685eb7df72156d0d0b49786e6 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Wed, 1 Apr 2026 14:34:44 -0500 Subject: [PATCH 01/11] controller: add features.yaml config loading with flex_algo flag and ShouldStamp helper --- .../controller/cmd/controller/main.go | 46 ++++++--- .../internal/controller/features_config.go | 68 ++++++++++++++ .../controller/features_config_test.go | 94 +++++++++++++++++++ .../controller/internal/controller/server.go | 8 ++ 4 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 controlplane/controller/internal/controller/features_config.go create mode 100644 controlplane/controller/internal/controller/features_config_test.go 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/server.go b/controlplane/controller/internal/controller/server.go index 37b51569b2..092bbb4110 100644 --- a/controlplane/controller/internal/controller/server.go +++ b/controlplane/controller/internal/controller/server.go @@ -72,6 +72,7 @@ type Controller struct { environment string deviceLocalASN uint32 clickhouse *ClickhouseWriter + featuresConfig *FeaturesConfig } type Option func(*Controller) @@ -160,6 +161,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) { From 9e375c24a142b24e3780e2010000e0917e767b90 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Wed, 1 Apr 2026 15:30:45 -0500 Subject: [PATCH 02/11] controller: add IS-IS TE admin-group, flex-algo topology config, and BGP color stamping --- .../controller/internal/controller/models.go | 22 +++ .../internal/controller/render_test.go | 169 ++++++++++++++++++ .../controller/internal/controller/server.go | 83 ++++++++- .../internal/controller/server_test.go | 16 +- .../internal/controller/templates/tunnel.tmpl | 41 +++++ .../sdk/go/serviceability/bytereader.go | 4 + smartcontract/sdk/go/serviceability/client.go | 8 + .../sdk/go/serviceability/client_test.go | 11 +- .../sdk/go/serviceability/deserialize.go | 14 +- smartcontract/sdk/go/serviceability/state.go | 10 +- .../sdk/go/serviceability/state_test.go | 8 +- 11 files changed, 355 insertions(+), 31 deletions(-) diff --git a/controlplane/controller/internal/controller/models.go b/controlplane/controller/internal/controller/models.go index dcf970c675..d9d5e5044f 100644 --- a/controlplane/controller/internal/controller/models.go +++ b/controlplane/controller/internal/controller/models.go @@ -45,6 +45,9 @@ 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 } // toInterface validates onchain data for a serviceability interface and converts it to a controller interface. @@ -242,6 +245,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 @@ -303,4 +309,20 @@ 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" } diff --git a/controlplane/controller/internal/controller/render_test.go b/controlplane/controller/internal/controller/render_test.go index 6d5b2b31aa..3f7c96efbe 100644 --- a/controlplane/controller/internal/controller/render_test.go +++ b/controlplane/controller/internal/controller/render_test.go @@ -829,3 +829,172 @@ func TestRenderConfig(t *testing.T) { }) } } + +func TestRenderFlexAlgoEnabled(t *testing.T) { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.CommunityStamping.All = true + + data := templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: cfg, + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 0, + FlexAlgoNumber: 128, + Color: 1, + 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"}, + }, + }, + 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", + }, + }, + }, + } + + got, err := renderConfig(data) + if err != nil { + t.Fatalf("error rendering template: %v", err) + } + + checks := []struct { + desc string + present bool + substr string + }{ + {"admin-group on interface", true, "traffic-engineering administrative-group unicast-default"}, + {"router traffic-engineering block", true, "router traffic-engineering"}, + {"admin-group alias definition", true, "administrative-group alias unicast-default group 0"}, + {"UNICAST-DRAINED alias", true, "administrative-group alias UNICAST-DRAINED group 1"}, + {"flex-algo definition in TE block", true, "flex-algo 128 unicast-default"}, + {"IS-IS flex-algo advertisement", true, "flex-algo 128"}, + {"next-hop resolution ribs in vpn-ipv4", true, "next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib"}, + {"extcommunity color in route-map", true, "set extcommunity color color 1"}, + } + + for _, c := range checks { + t.Run(c.desc, func(t *testing.T) { + if strings.Contains(got, c.substr) != c.present { + if c.present { + t.Errorf("expected %q to be present in rendered config, but it was not", c.substr) + } else { + t.Errorf("expected %q to be absent from rendered config, but it was present", c.substr) + } + } + }) + } +} + +func TestRenderFlexAlgoDisabled(t *testing.T) { + // Config is nil — flex-algo blocks must not appear. + 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", + }, + }, + }, + } + + got, err := renderConfig(data) + if err != nil { + t.Fatalf("error rendering template: %v", err) + } + + absent := []string{ + "traffic-engineering administrative-group", + "router traffic-engineering", + "administrative-group alias", + "next-hop resolution ribs", + "set extcommunity color", + } + + for _, substr := range absent { + t.Run("absent: "+substr, func(t *testing.T) { + if strings.Contains(got, substr) { + t.Errorf("expected %q to be absent from rendered config (flex-algo disabled), but it was present", substr) + } + }) + } +} diff --git a/controlplane/controller/internal/controller/server.go b/controlplane/controller/internal/controller/server.go index 092bbb4110..66ac482f13 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 @@ -268,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: @@ -356,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 @@ -405,6 +412,16 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } d.Interfaces[i].IsLink = true d.Interfaces[i].LinkStatus = link.Status + d.Interfaces[i].UnicastDrained = link.UnicastDrained + + // 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)) } @@ -520,6 +537,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 @@ -527,6 +546,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) + } } } @@ -608,6 +631,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 { @@ -702,6 +732,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() @@ -805,6 +858,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, @@ -816,6 +883,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..41fc0459a7 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}, }, @@ -1077,6 +1080,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 +1110,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 +1178,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 +1224,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 +1306,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 +1362,7 @@ func TestStateCache(t *testing.T) { Allocated: true, VrfId: 1, MetroRouting: true, + TenantPubKey: "11111111111111111111111111111111", }, { Id: 501, @@ -1366,6 +1375,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 +1489,7 @@ func TestStateCache(t *testing.T) { VrfId: 2, }, }, + Topologies: map[string]serviceability.TopologyInfo{}, UnicastVrfs: []uint16{1, 2}, Vpnv4BgpPeers: []BgpPeer{ { @@ -1513,6 +1524,7 @@ func TestStateCache(t *testing.T) { PubKey: "11111111111111111111111111111111", Allocated: true, VrfId: 1, + TenantPubKey: "g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT", }, { Id: 501, @@ -1524,6 +1536,7 @@ func TestStateCache(t *testing.T) { PubKey: "11111111111111111111111111111111", Allocated: true, VrfId: 2, + TenantPubKey: "2M59vuWgsiuHAqQVB6KvuXuaBCJR8138gMAm4uCuR6Du", }, { Id: 502, @@ -1536,6 +1549,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..424ec0aa97 100644 --- a/controlplane/controller/internal/controller/templates/tunnel.tmpl +++ b/controlplane/controller/internal/controller/templates/tunnel.tmpl @@ -123,6 +123,11 @@ interface {{ .Name }} {{- end }} isis hello padding isis network point-to-point + {{- if and $.FlexAlgoEnabled .IsLink .LinkTopologies }} + traffic-engineering administrative-group {{ range $i, $name := .LinkTopologies }}{{ if $i }} {{ end }}{{ $name }}{{ end }}{{ if .UnicastDrained }} UNICAST-DRAINED{{ end }} + {{- else if and $.FlexAlgoEnabled .IsLink .UnicastDrained }} + traffic-engineering administrative-group UNICAST-DRAINED + {{- end }} {{- end }} ! {{- end }} @@ -247,6 +252,9 @@ 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 + {{- end }} ! {{- range $vrfId := .UnicastVrfs }} vrf vrf{{ $vrfId }} @@ -284,12 +292,40 @@ router isis 1 ! segment-routing mpls no shutdown +{{- if $.FlexAlgoEnabled }} + flex-algo + {{- range $.AllTopologies }} + {{- if eq .ConstraintStr "include-any" }} + flex-algo {{ .FlexAlgoNumber }} + ! + {{- end }} + {{- end }} +{{- end }} {{- if $.Device.Status.IsDrained }} set-overload-bit {{- else }} no set-overload-bit {{- end }} ! +{{- if $.FlexAlgoEnabled }} +router traffic-engineering + {{- range $.AllTopologies }} + administrative-group alias {{ .Name }} group {{ .AdminGroupBit }} + {{- end }} + administrative-group alias UNICAST-DRAINED group 1 + flex-algo + {{- range $.AllTopologies }} + {{- if eq .ConstraintStr "include-any" }} + flex-algo {{ .FlexAlgoNumber }} {{ .Name }} + metric-type igp + color {{ .Color }} + topology standard + exclude 1 + ! + {{- end }} + {{- end }} +! +{{- 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 +349,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 color {{ .TenantTopologyColors }} + {{- end }} + {{- end }} {{- end }} ! {{- end }} diff --git a/smartcontract/sdk/go/serviceability/bytereader.go b/smartcontract/sdk/go/serviceability/bytereader.go index 441b36b7e3..2232e838a5 100644 --- a/smartcontract/sdk/go/serviceability/bytereader.go +++ b/smartcontract/sdk/go/serviceability/bytereader.go @@ -44,6 +44,10 @@ func (br *ByteReader) ReadU8() uint8 { return val } +func (br *ByteReader) ReadBool() bool { + return br.ReadU8() != 0 +} + func (br *ByteReader) ReadU16() uint16 { if br.offset+2 > len(br.data) { return 0 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..7389a3949a 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{}, }, }, { @@ -408,6 +413,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -441,6 +447,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -479,6 +486,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { ProgramConfig: ProgramConfig{}, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, { @@ -505,6 +513,7 @@ func TestSDK_Serviceability_GetProgramData(t *testing.T) { }, ResourceExtensions: []ResourceExtension{}, Permissions: []Permission{}, + Topologies: []TopologyInfo{}, }, }, } diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index b444b8b257..5e733b5ab1 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -90,8 +90,6 @@ func DeserializeInterface(reader *ByteReader, iface *Interface) { DeserializeInterfaceV1(reader, iface) case 1: // version 2 DeserializeInterfaceV2(reader, iface) - case 2: // version 3 - DeserializeInterfaceV3(reader, iface) } } @@ -122,16 +120,6 @@ func DeserializeInterfaceV2(reader *ByteReader, iface *Interface) { iface.IpNet = reader.ReadNetworkV4() iface.NodeSegmentIdx = reader.ReadU16() iface.UserTunnelEndpoint = (reader.ReadU8() != 0) - // flex_algo_node_segments was merged into V2 from the old V3. - // Old V2 accounts (written before this field existed) will have no trailing - // bytes — ReadFlexAlgoNodeSegmentSlice returns nil/empty in that case. - iface.FlexAlgoNodeSegments = reader.ReadFlexAlgoNodeSegmentSlice() -} - -// DeserializeInterfaceV3 handles legacy on-chain accounts written with -// discriminant 2 (the old V3). Their layout is identical to the current V2. -func DeserializeInterfaceV3(reader *ByteReader, iface *Interface) { - DeserializeInterfaceV2(reader, iface) } func DeserializeDevice(reader *ByteReader, dev *Device) { @@ -198,7 +186,7 @@ func DeserializeLink(reader *ByteReader, link *Link) { link.LinkHealth = LinkHealth(reader.ReadU8()) link.LinkDesiredStatus = LinkDesiredStatus(reader.ReadU8()) link.LinkTopologies = reader.ReadPubkeySlice() - link.LinkFlags = reader.ReadU8() + link.UnicastDrained = reader.ReadBool() } func DeserializeUser(reader *ByteReader, user *User) { diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index 2201269f36..0f5c0893e0 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -371,9 +371,9 @@ type Interface struct { RoutingMode RoutingMode VlanId uint16 IpNet [5]uint8 - NodeSegmentIdx uint16 - UserTunnelEndpoint bool - FlexAlgoNodeSegments []FlexAlgoNodeSegment `json:",omitempty"` + NodeSegmentIdx uint16 + UserTunnelEndpoint bool + FlexAlgoNodeSegments []FlexAlgoNodeSegment `json:",omitempty"` } func (i Interface) MarshalJSON() ([]byte, error) { @@ -398,7 +398,7 @@ func (i Interface) MarshalJSON() ([]byte, error) { return json.Marshal(jsonIface) } -const CurrentInterfaceVersion = 3 +const CurrentInterfaceVersion = 2 type Device struct { AccountType AccountType @@ -619,7 +619,7 @@ type Link struct { LinkDesiredStatus LinkDesiredStatus `influx:"tag,link_desired_status"` PubKey [32]byte `influx:"tag,pubkey,pubkey"` LinkTopologies [][32]byte - LinkFlags uint8 + UnicastDrained bool } func (l Link) MarshalJSON() ([]byte, error) { diff --git a/smartcontract/sdk/go/serviceability/state_test.go b/smartcontract/sdk/go/serviceability/state_test.go index e247e2bf27..1cfb11eab6 100644 --- a/smartcontract/sdk/go/serviceability/state_test.go +++ b/smartcontract/sdk/go/serviceability/state_test.go @@ -72,7 +72,7 @@ func TestCustomJSONMarshal(t *testing.T) { "SideZIfaceName": "Switch1/1/1", "DelayOverrideNs": 10, "LinkTopologies": null, - "LinkFlags": 0, + "UnicastDrained": false, "PubKey": "` + dummyPubKeyB58 + `" }`, expectErr: false, @@ -125,7 +125,7 @@ func TestCustomJSONMarshal(t *testing.T) { "SideZIfaceName": "Edge2/0/0", "DelayOverrideNs": 0, "LinkTopologies": null, - "LinkFlags": 0, + "UnicastDrained": false, "PubKey": "` + dummyPubKeyB58 + `" }`, expectErr: false, @@ -158,7 +158,7 @@ func TestCustomJSONMarshal(t *testing.T) { "SideZIfaceName": "", "DelayOverrideNs": 0, "LinkTopologies": null, - "LinkFlags": 0, + "UnicastDrained": false, "PubKey": "11111111111111111111111111111111" }`, expectErr: false, @@ -310,7 +310,7 @@ func TestCustomJSONMarshal(t *testing.T) { "MgmtVrf": "mgmt-vrf", "Interfaces": [ { - "Version": 2, + "Version": 1, "Status": "activated", "Name": "Switch1/1/1", "InterfaceType": "physical", From 6e071b88d18587c4e02aef3aafbed0eab4d804eb Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Wed, 1 Apr 2026 16:02:35 -0500 Subject: [PATCH 03/11] controller: fix flex-algo template EOS syntax; add loopback node-segment support --- .../controller/internal/controller/models.go | 23 ++++++++++- .../internal/controller/render_test.go | 37 ++++++++++++++--- .../controller/internal/controller/server.go | 40 +++++++++++++++++++ .../internal/controller/server_test.go | 3 ++ .../internal/controller/templates/tunnel.tmpl | 40 ++++++++++++------- .../sdk/go/serviceability/deserialize.go | 1 + smartcontract/sdk/go/serviceability/state.go | 6 +-- 7 files changed, 125 insertions(+), 25 deletions(-) diff --git a/controlplane/controller/internal/controller/models.go b/controlplane/controller/internal/controller/models.go index d9d5e5044f..87813b792b 100644 --- a/controlplane/controller/internal/controller/models.go +++ b/controlplane/controller/internal/controller/models.go @@ -48,11 +48,14 @@ type Interface struct { // 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") } @@ -298,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 @@ -326,3 +341,9 @@ type TopologyModel struct { 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 3f7c96efbe..aa326559fe 100644 --- a/controlplane/controller/internal/controller/render_test.go +++ b/controlplane/controller/internal/controller/render_test.go @@ -870,6 +870,16 @@ func TestRenderFlexAlgoEnabled(t *testing.T) { 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{ { @@ -899,14 +909,29 @@ func TestRenderFlexAlgoEnabled(t *testing.T) { present bool substr string }{ - {"admin-group on interface", true, "traffic-engineering administrative-group unicast-default"}, - {"router traffic-engineering block", true, "router traffic-engineering"}, - {"admin-group alias definition", true, "administrative-group alias unicast-default group 0"}, - {"UNICAST-DRAINED alias", true, "administrative-group alias UNICAST-DRAINED group 1"}, - {"flex-algo definition in TE block", true, "flex-algo 128 unicast-default"}, - {"IS-IS flex-algo advertisement", true, "flex-algo 128"}, + // Change 1: interface admin-group (uppercase) + {"traffic-engineering enable on interface", true, " traffic-engineering\n traffic-engineering administrative-group UNICAST-DEFAULT"}, + // Change 3: BGP next-hop {"next-hop resolution ribs in vpn-ipv4", true, "next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib"}, + // Change 4: IS-IS flex-algo advertisement inside segment-routing mpls + {"IS-IS flex-algo advertisement", true, "flex-algo unicast-default level-2 advertised"}, + {"IS-IS traffic-engineering block", true, " traffic-engineering\n no shutdown\n is-type level-2"}, + // Change 5: router traffic-engineering block (correct structure) + {"router traffic-engineering block", true, "router traffic-engineering"}, + {"router-id in TE block", true, " router-id ipv4 14.14.14.14"}, + {"UNICAST-DRAINED alias first", true, " administrative-group alias UNICAST-DRAINED group 1"}, + {"UNICAST-DEFAULT alias uppercase", true, " administrative-group alias UNICAST-DEFAULT group 0"}, + {"flex-algo TE definition", true, " flex-algo 128 unicast-default"}, + {"flex-algo admin-group include-any", true, " administrative-group include any 0 exclude 1"}, + {"flex-algo color", true, " color 1"}, + // Community stamping {"extcommunity color in route-map", true, "set extcommunity color color 1"}, + // Change 2: loopback node-segments + {"loopback base node-segment", true, " node-segment ipv4 index 100"}, + {"loopback flex-algo node-segment", true, " node-segment ipv4 index 200 flex-algo unicast-default"}, + // Negative: wrong patterns must not appear + {"no metric-type igp", false, "metric-type igp"}, + {"no topology standard", false, "topology standard"}, } for _, c := range checks { diff --git a/controlplane/controller/internal/controller/server.go b/controlplane/controller/internal/controller/server.go index 66ac482f13..655ca3000d 100644 --- a/controlplane/controller/internal/controller/server.go +++ b/controlplane/controller/internal/controller/server.go @@ -413,6 +413,7 @@ func (c *Controller) updateStateCache(ctx context.Context) error { d.Interfaces[i].IsLink = true d.Interfaces[i].LinkStatus = link.Status d.Interfaces[i].UnicastDrained = link.UnicastDrained + d.Interfaces[i].PubKey = base58.Encode(link.PubKey[:]) // Resolve topology names from link_topologies pubkeys for _, topoKey := range link.LinkTopologies { @@ -425,6 +426,29 @@ func (c *Controller) updateStateCache(ctx context.Context) error { 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 } @@ -587,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) diff --git a/controlplane/controller/internal/controller/server_test.go b/controlplane/controller/internal/controller/server_test.go index 41fc0459a7..51d8acfebc 100644 --- a/controlplane/controller/internal/controller/server_test.go +++ b/controlplane/controller/internal/controller/server_test.go @@ -992,6 +992,7 @@ func TestStateCache(t *testing.T) { IsLink: true, Metric: 400000, LinkStatus: serviceability.LinkStatusActivated, + PubKey: "11111111111111111111111111111111", }, { InterfaceType: InterfaceTypePhysical, @@ -1001,6 +1002,7 @@ func TestStateCache(t *testing.T) { IsLink: true, Metric: 1, LinkStatus: serviceability.LinkStatusActivated, + PubKey: "11111111111111111111111111111111", }, { InterfaceType: InterfaceTypePhysical, @@ -1010,6 +1012,7 @@ func TestStateCache(t *testing.T) { IsLink: true, Metric: 50, LinkStatus: serviceability.LinkStatusActivated, + PubKey: "11111111111111111111111111111111", }, { InterfaceType: InterfaceTypeLoopback, diff --git a/controlplane/controller/internal/controller/templates/tunnel.tmpl b/controlplane/controller/internal/controller/templates/tunnel.tmpl index 424ec0aa97..9d013584c4 100644 --- a/controlplane/controller/internal/controller/templates/tunnel.tmpl +++ b/controlplane/controller/internal/controller/templates/tunnel.tmpl @@ -107,6 +107,11 @@ 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 }} + {{- end }} {{- end }} {{- if and .Ip.IsValid .IsLoopback }} isis enable 1 @@ -123,10 +128,15 @@ interface {{ .Name }} {{- end }} isis hello padding isis network point-to-point - {{- if and $.FlexAlgoEnabled .IsLink .LinkTopologies }} - traffic-engineering administrative-group {{ range $i, $name := .LinkTopologies }}{{ if $i }} {{ end }}{{ $name }}{{ end }}{{ if .UnicastDrained }} UNICAST-DRAINED{{ end }} - {{- else if and $.FlexAlgoEnabled .IsLink .UnicastDrained }} + {{- if and $.FlexAlgoEnabled .Ip.IsValid .IsPhysical .Metric .IsLink (not .IsSubInterfaceParent) (not .IsCYOA) (not .IsDIA) }} + 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 }} {{- end }} {{- end }} ! @@ -292,15 +302,16 @@ router isis 1 ! segment-routing mpls no shutdown -{{- if $.FlexAlgoEnabled }} - flex-algo - {{- range $.AllTopologies }} - {{- if eq .ConstraintStr "include-any" }} - flex-algo {{ .FlexAlgoNumber }} - ! + {{- if $.FlexAlgoEnabled }} + {{- range $.AllTopologies }} + flex-algo {{ .Name }} level-2 advertised {{- end }} + {{- end }} + {{- if $.FlexAlgoEnabled }} + traffic-engineering + no shutdown + is-type level-2 {{- end }} -{{- end }} {{- if $.Device.Status.IsDrained }} set-overload-bit {{- else }} @@ -309,18 +320,17 @@ router isis 1 ! {{- if $.FlexAlgoEnabled }} router traffic-engineering + router-id ipv4 {{ $.Device.Vpn4vLoopbackIP }} + administrative-group alias UNICAST-DRAINED group 1 {{- range $.AllTopologies }} - administrative-group alias {{ .Name }} group {{ .AdminGroupBit }} + administrative-group alias {{ $.Strings.ToUpper .Name }} group {{ .AdminGroupBit }} {{- end }} - administrative-group alias UNICAST-DRAINED group 1 flex-algo {{- range $.AllTopologies }} {{- if eq .ConstraintStr "include-any" }} flex-algo {{ .FlexAlgoNumber }} {{ .Name }} - metric-type igp + administrative-group include any {{ .AdminGroupBit }} exclude 1 color {{ .Color }} - topology standard - exclude 1 ! {{- end }} {{- end }} diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index 5e733b5ab1..07d82e28f0 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -120,6 +120,7 @@ func DeserializeInterfaceV2(reader *ByteReader, iface *Interface) { iface.IpNet = reader.ReadNetworkV4() iface.NodeSegmentIdx = reader.ReadU16() iface.UserTunnelEndpoint = (reader.ReadU8() != 0) + iface.FlexAlgoNodeSegments = reader.ReadFlexAlgoNodeSegmentSlice() } func DeserializeDevice(reader *ByteReader, dev *Device) { diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index 0f5c0893e0..a9605b4fa7 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -371,9 +371,9 @@ type Interface struct { RoutingMode RoutingMode VlanId uint16 IpNet [5]uint8 - NodeSegmentIdx uint16 - UserTunnelEndpoint bool - FlexAlgoNodeSegments []FlexAlgoNodeSegment `json:",omitempty"` + NodeSegmentIdx uint16 + UserTunnelEndpoint bool + FlexAlgoNodeSegments []FlexAlgoNodeSegment `json:",omitempty"` } func (i Interface) MarshalJSON() ([]byte, error) { From 7f65eefd00a8e98a0c459b56742426f07be776cf Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Thu, 2 Apr 2026 21:42:36 -0500 Subject: [PATCH 04/11] controller: add golden fixture tests; fix extcommunity template bug set extcommunity color {{ .TenantTopologyColors }} produced "set extcommunity color color 1" because TenantTopologyColors already includes the "color N" prefix. Removed the redundant "color" keyword from the template. Converted flex-algo render tests from substring assertions to golden fixtures with -update flag regeneration. Added four scenarios: single topology, multi-topology (unicast-default + high-bandwidth + CYOA), unicast-drained, and link-excluded. --- .../controller/fixtures/flex_algo.tunnel.tmpl | 212 +++++++ .../flex_algo_link_excluded.tunnel.tmpl | 209 +++++++ .../flex_algo_multi_topology.tunnel.tmpl | 254 ++++++++ .../flex_algo_unicast_drained.tunnel.tmpl | 227 ++++++++ .../internal/controller/render_test.go | 544 +++++++++++------- .../internal/controller/templates/tunnel.tmpl | 2 +- 6 files changed, 1254 insertions(+), 194 deletions(-) create mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl create mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo_link_excluded.tunnel.tmpl create mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl create mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo_unicast_drained.tunnel.tmpl diff --git a/controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl new file mode 100644 index 0000000000..2eb4c2b539 --- /dev/null +++ b/controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl @@ -0,0 +1,212 @@ +! +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 + administrative-group alias UNICAST-DRAINED group 1 + administrative-group alias UNICAST-DEFAULT group 0 + flex-algo + flex-algo 128 unicast-default + administrative-group include any 0 exclude 1 + color 1 + ! +! +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 1 +! +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/flex_algo_link_excluded.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_link_excluded.tunnel.tmpl new file mode 100644 index 0000000000..6cb74a2ae0 --- /dev/null +++ b/controlplane/controller/internal/controller/fixtures/flex_algo_link_excluded.tunnel.tmpl @@ -0,0 +1,209 @@ +! +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 + no traffic-engineering administrative-group +! +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 + administrative-group alias UNICAST-DRAINED group 1 + administrative-group alias UNICAST-DEFAULT group 0 + flex-algo + flex-algo 128 unicast-default + administrative-group include any 0 exclude 1 + color 1 + ! +! +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 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/flex_algo_multi_topology.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl new file mode 100644 index 0000000000..173e88aa72 --- /dev/null +++ b/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl @@ -0,0 +1,254 @@ +! +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 Ethernet1/2 + mtu 2048 + no switchport + ip address 172.16.1.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 Ethernet2/1 + mtu 9000 + no switchport + ip address 172.16.2.2/31 + pim ipv4 sparse-mode + isis enable 1 + isis circuit-type level-2 + isis hello-interval 1 + isis metric 10000 + no isis passive + isis hello padding + isis network point-to-point + traffic-engineering + traffic-engineering administrative-group UNICAST-DEFAULT HIGH-BANDWIDTH +! +interface Ethernet3 + mtu 9216 + no switchport + ip address 10.0.0.1/24 +! +interface Loopback255 + ip address 14.14.14.14/32 + node-segment ipv4 index 100 + node-segment ipv4 index 200 flex-algo unicast-default + node-segment ipv4 index 201 flex-algo high-bandwidth + 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 + flex-algo high-bandwidth 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 + administrative-group alias UNICAST-DRAINED group 1 + administrative-group alias UNICAST-DEFAULT group 0 + administrative-group alias HIGH-BANDWIDTH group 2 + flex-algo + flex-algo 128 unicast-default + administrative-group include any 0 exclude 1 + color 1 + ! + flex-algo 129 high-bandwidth + administrative-group include any 2 exclude 1 + 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 1 +! +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/flex_algo_unicast_drained.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_unicast_drained.tunnel.tmpl new file mode 100644 index 0000000000..2516d51d73 --- /dev/null +++ b/controlplane/controller/internal/controller/fixtures/flex_algo_unicast_drained.tunnel.tmpl @@ -0,0 +1,227 @@ +! +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 Ethernet1/2 + mtu 2048 + no switchport + ip address 172.16.1.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 UNICAST-DRAINED +! +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 + administrative-group alias UNICAST-DRAINED group 1 + administrative-group alias UNICAST-DEFAULT group 0 + flex-algo + flex-algo 128 unicast-default + administrative-group include any 0 exclude 1 + color 1 + ! +! +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 1 +! +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/render_test.go b/controlplane/controller/internal/controller/render_test.go index aa326559fe..4814f65fa6 100644 --- a/controlplane/controller/internal/controller/render_test.go +++ b/controlplane/controller/internal/controller/render_test.go @@ -1,6 +1,7 @@ package controller import ( + "flag" "net" "net/netip" "os" @@ -12,6 +13,8 @@ import ( "github.com/malbeclabs/doublezero/smartcontract/sdk/go/serviceability" ) +var update = flag.Bool("update", false, "update golden fixture files") + func TestRenderConfig(t *testing.T) { tests := []struct { Name string @@ -797,6 +800,348 @@ func TestRenderConfig(t *testing.T) { }, Want: "fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl", }, + { + Name: "render_flex_algo_enabled", + Description: "render config with flex-algo IS-IS topology config and BGP community stamping enabled", + Data: func() templateData { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.CommunityStamping.All = true + return templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: cfg, + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 0, + FlexAlgoNumber: 128, + Color: 1, + 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 1", + }, + }, + }, + } + }(), + Want: "fixtures/flex_algo.tunnel.tmpl", + }, + { + Name: "render_flex_algo_multi_topology", + Description: "render config with two flex-algo topologies: two WAN links in unicast-default, one WAN link in both unicast-default and high-bandwidth, one CYOA port not participating in flex-algo", + Data: func() templateData { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.CommunityStamping.All = true + return templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: cfg, + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 0, + FlexAlgoNumber: 128, + Color: 1, + ConstraintStr: "include-any", + }, + { + Name: "high-bandwidth", + AdminGroupBit: 2, + 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{ + { + // WAN link — unicast-default only + 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"}, + }, + { + // WAN link — unicast-default only (second peer) + Name: "Ethernet1/2", + Ip: netip.MustParsePrefix("172.16.1.2/31"), + Mtu: 2048, + InterfaceType: InterfaceTypePhysical, + Metric: 40000, + IsLink: true, + LinkStatus: serviceability.LinkStatusActivated, + LinkTopologies: []string{"unicast-default"}, + }, + { + // High-capacity WAN link — participates in both topologies + Name: "Ethernet2/1", + Ip: netip.MustParsePrefix("172.16.2.2/31"), + Mtu: 9000, + InterfaceType: InterfaceTypePhysical, + Metric: 10000, + IsLink: true, + LinkStatus: serviceability.LinkStatusActivated, + LinkTopologies: []string{"unicast-default", "high-bandwidth"}, + }, + { + // CYOA port — client-facing, not participating in flex-algo + Name: "Ethernet3", + Ip: netip.MustParsePrefix("10.0.0.1/24"), + Mtu: 9216, + InterfaceType: InterfaceTypePhysical, + IsCYOA: true, + }, + { + Name: "Loopback255", + Ip: netip.MustParsePrefix("14.14.14.14/32"), + NodeSegmentIdx: 100, + InterfaceType: InterfaceTypeLoopback, + LoopbackType: LoopbackTypeVpnv4, + FlexAlgoNodeSegments: []FlexAlgoNodeSegmentModel{ + {NodeSegmentIdx: 200, TopologyName: "unicast-default"}, + {NodeSegmentIdx: 201, TopologyName: "high-bandwidth"}, + }, + }, + }, + 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/flex_algo_multi_topology.tunnel.tmpl", + }, + { + Name: "render_flex_algo_unicast_drained", + Description: "render config with flex-algo enabled: one healthy WAN link and one draining WAN link — draining link appends UNICAST-DRAINED to admin-group", + Data: func() templateData { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.CommunityStamping.All = true + return templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: cfg, + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 0, + FlexAlgoNumber: 128, + Color: 1, + 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{ + { + // Healthy WAN link — normal admin-group + 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"}, + }, + { + // Draining WAN link — UNICAST-DRAINED appended + Name: "Ethernet1/2", + Ip: netip.MustParsePrefix("172.16.1.2/31"), + Mtu: 2048, + InterfaceType: InterfaceTypePhysical, + Metric: 40000, + IsLink: true, + LinkStatus: serviceability.LinkStatusActivated, + LinkTopologies: []string{"unicast-default"}, + UnicastDrained: true, + }, + { + 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 1", + }, + }, + }, + } + }(), + Want: "fixtures/flex_algo_unicast_drained.tunnel.tmpl", + }, + { + Name: "render_flex_algo_link_excluded", + Description: "render config with flex-algo enabled but a link excluded from tagging — renders no traffic-engineering administrative-group", + Data: func() templateData { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.LinkTagging.Exclude.Links = []string{"LinkPubKeyExcluded111111111111111111111111111"} + return templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: cfg, + AllTopologies: []TopologyModel{ + { + Name: "unicast-default", + AdminGroupBit: 0, + FlexAlgoNumber: 128, + Color: 1, + 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"}, + PubKey: "LinkPubKeyExcluded111111111111111111111111111", + }, + { + 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, + }, + }, + }, + } + }(), + Want: "fixtures/flex_algo_link_excluded.tunnel.tmpl", + }, } for _, test := range tests { @@ -805,6 +1150,12 @@ func TestRenderConfig(t *testing.T) { if err != nil { t.Fatalf("error rendering template: %v", err) } + if *update { + if err := os.WriteFile(test.Want, []byte(got), 0o644); err != nil { + t.Fatalf("error writing fixture %s: %v", test.Want, err) + } + return + } var want []byte if strings.HasSuffix(test.Want, ".tmpl") { templateData := map[string]int{ @@ -830,196 +1181,3 @@ func TestRenderConfig(t *testing.T) { } } -func TestRenderFlexAlgoEnabled(t *testing.T) { - cfg := &FeaturesConfig{} - cfg.Features.FlexAlgo.Enabled = true - cfg.Features.FlexAlgo.CommunityStamping.All = true - - data := templateData{ - Strings: StringsHelper{}, - MulticastGroupBlock: "239.0.0.0/24", - TelemetryTWAMPListenPort: 862, - LocalASN: 65342, - UnicastVrfs: []uint16{1}, - Config: cfg, - AllTopologies: []TopologyModel{ - { - Name: "unicast-default", - AdminGroupBit: 0, - FlexAlgoNumber: 128, - Color: 1, - 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 1", - }, - }, - }, - } - - got, err := renderConfig(data) - if err != nil { - t.Fatalf("error rendering template: %v", err) - } - - checks := []struct { - desc string - present bool - substr string - }{ - // Change 1: interface admin-group (uppercase) - {"traffic-engineering enable on interface", true, " traffic-engineering\n traffic-engineering administrative-group UNICAST-DEFAULT"}, - // Change 3: BGP next-hop - {"next-hop resolution ribs in vpn-ipv4", true, "next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib"}, - // Change 4: IS-IS flex-algo advertisement inside segment-routing mpls - {"IS-IS flex-algo advertisement", true, "flex-algo unicast-default level-2 advertised"}, - {"IS-IS traffic-engineering block", true, " traffic-engineering\n no shutdown\n is-type level-2"}, - // Change 5: router traffic-engineering block (correct structure) - {"router traffic-engineering block", true, "router traffic-engineering"}, - {"router-id in TE block", true, " router-id ipv4 14.14.14.14"}, - {"UNICAST-DRAINED alias first", true, " administrative-group alias UNICAST-DRAINED group 1"}, - {"UNICAST-DEFAULT alias uppercase", true, " administrative-group alias UNICAST-DEFAULT group 0"}, - {"flex-algo TE definition", true, " flex-algo 128 unicast-default"}, - {"flex-algo admin-group include-any", true, " administrative-group include any 0 exclude 1"}, - {"flex-algo color", true, " color 1"}, - // Community stamping - {"extcommunity color in route-map", true, "set extcommunity color color 1"}, - // Change 2: loopback node-segments - {"loopback base node-segment", true, " node-segment ipv4 index 100"}, - {"loopback flex-algo node-segment", true, " node-segment ipv4 index 200 flex-algo unicast-default"}, - // Negative: wrong patterns must not appear - {"no metric-type igp", false, "metric-type igp"}, - {"no topology standard", false, "topology standard"}, - } - - for _, c := range checks { - t.Run(c.desc, func(t *testing.T) { - if strings.Contains(got, c.substr) != c.present { - if c.present { - t.Errorf("expected %q to be present in rendered config, but it was not", c.substr) - } else { - t.Errorf("expected %q to be absent from rendered config, but it was present", c.substr) - } - } - }) - } -} - -func TestRenderFlexAlgoDisabled(t *testing.T) { - // Config is nil — flex-algo blocks must not appear. - 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", - }, - }, - }, - } - - got, err := renderConfig(data) - if err != nil { - t.Fatalf("error rendering template: %v", err) - } - - absent := []string{ - "traffic-engineering administrative-group", - "router traffic-engineering", - "administrative-group alias", - "next-hop resolution ribs", - "set extcommunity color", - } - - for _, substr := range absent { - t.Run("absent: "+substr, func(t *testing.T) { - if strings.Contains(got, substr) { - t.Errorf("expected %q to be absent from rendered config (flex-algo disabled), but it was present", substr) - } - }) - } -} diff --git a/controlplane/controller/internal/controller/templates/tunnel.tmpl b/controlplane/controller/internal/controller/templates/tunnel.tmpl index 9d013584c4..4bff223206 100644 --- a/controlplane/controller/internal/controller/templates/tunnel.tmpl +++ b/controlplane/controller/internal/controller/templates/tunnel.tmpl @@ -361,7 +361,7 @@ route-map RM-USER-{{ .Id }}-IN permit 10 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 color {{ .TenantTopologyColors }} + set extcommunity {{ .TenantTopologyColors }} {{- end }} {{- end }} {{- end }} From 059472367bb67ba50ffa76759343d9bcf6221828 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Thu, 2 Apr 2026 21:54:43 -0500 Subject: [PATCH 05/11] controller: extend multi_topology test with two tunnels for per-tunnel color stamping --- .../flex_algo_multi_topology.tunnel.tmpl | 60 +++++++++++++++++++ .../internal/controller/render_test.go | 22 +++++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl index 173e88aa72..7b2bd1739e 100644 --- a/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl @@ -19,8 +19,10 @@ router pim sparse-mode rp address 10.0.0.0 239.0.0.0/24 override ! vrf instance vrf1 +vrf instance vrf2 ip routing ip routing vrf vrf1 +ip routing vrf vrf2 ! ntp server 0.pool.ntp.org ntp server 1.pool.ntp.org @@ -131,6 +133,7 @@ mpls icmp ttl-exceeded tunneling mpls icmp ip source-interface Loopback255 ! default interface Tunnel500 +default interface Tunnel501 interface Tunnel500 description USER-UCAST-500 ip access-group SEC-USER-500-IN in @@ -144,6 +147,19 @@ interface Tunnel500 tunnel ttl 32 no shutdown ! +interface Tunnel501 + description USER-UCAST-501 + ip access-group SEC-USER-501-IN in + vrf vrf2 + mtu 9216 + ip address 169.254.1.0/31 + tunnel mode gre + tunnel source 1.1.1.1 + tunnel destination 3.3.3.3 + tunnel path-mtu-discovery + tunnel ttl 32 + no shutdown +! router bgp 65342 router-id 14.14.14.14 timers bgp 1 3 @@ -167,6 +183,20 @@ router bgp 65342 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 + vrf vrf2 + rd 65342:2 + route-target import vpn-ipv4 65342:2 + route-target export vpn-ipv4 65342:2 + router-id 7.7.7.7 + no neighbor 169.254.1.1 + neighbor 169.254.1.1 remote-as 65000 + neighbor 169.254.1.1 local-as 65342 no-prepend replace-as + neighbor 169.254.1.1 passive + neighbor 169.254.1.1 description USER-501 + neighbor 169.254.1.1 route-map RM-USER-501-IN in + neighbor 169.254.1.1 route-map RM-USER-501-OUT out + neighbor 169.254.1.1 maximum-routes 1 + neighbor 169.254.1.1 maximum-accepted-routes 1 ! router isis 1 net 49.0000.0e0e.0e0e.0000.00 @@ -209,6 +239,10 @@ route-map RM-USER-500-OUT deny 10 route-map RM-USER-500-OUT permit 20 match community COMM-ALL_USERS ! +no route-map RM-USER-501-OUT +route-map RM-USER-501-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 @@ -216,9 +250,19 @@ route-map RM-USER-500-IN permit 10 set community 21682:1200 21682:10050 set extcommunity color 1 ! +no route-map RM-USER-501-IN +route-map RM-USER-501-IN permit 10 + match ip address prefix-list PL-USER-501 + 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 prefix-list PL-USER-501 +ip prefix-list PL-USER-501 seq 10 permit 100.0.0.1/32 +! no ip access-list SEC-USER-500-IN ip access-list SEC-USER-500-IN counters per-entry @@ -234,7 +278,23 @@ ip access-list SEC-USER-500-IN permit ip 100.0.0.0/32 any deny ip any any ! +no ip access-list SEC-USER-501-IN +ip access-list SEC-USER-501-IN + counters per-entry + !ICMP + permit icmp 169.254.1.1/32 any + !TRACEROUTE + permit udp 169.254.1.1/32 any range 33434 33534 + !Allow TTL Exceeded for traceroute return packets + permit icmp 169.254.1.1/32 any time-exceeded + !PERMIT BGP + permit tcp 169.254.1.1/32 169.254.1.0/32 eq 179 + !PERMIT USER DZ_IP as a source only + permit ip 100.0.0.1/32 any + deny ip any any +! no ip access-list standard SEC-USER-MCAST-BOUNDARY-500-OUT +no ip access-list standard SEC-USER-MCAST-BOUNDARY-501-OUT no ip access-list SEC-USER-PUB-MCAST-IN ip access-list SEC-USER-PUB-MCAST-IN counters per-entry diff --git a/controlplane/controller/internal/controller/render_test.go b/controlplane/controller/internal/controller/render_test.go index 4814f65fa6..1e1e7de7e7 100644 --- a/controlplane/controller/internal/controller/render_test.go +++ b/controlplane/controller/internal/controller/render_test.go @@ -875,7 +875,7 @@ func TestRenderConfig(t *testing.T) { }, { Name: "render_flex_algo_multi_topology", - Description: "render config with two flex-algo topologies: two WAN links in unicast-default, one WAN link in both unicast-default and high-bandwidth, one CYOA port not participating in flex-algo", + Description: "render config with two flex-algo topologies: three tunnels verify per-tunnel color stamping — unicast-default only (color 1), high-bandwidth only (color 2), and both (color 1 color 2)", Data: func() templateData { cfg := &FeaturesConfig{} cfg.Features.FlexAlgo.Enabled = true @@ -885,7 +885,7 @@ func TestRenderConfig(t *testing.T) { MulticastGroupBlock: "239.0.0.0/24", TelemetryTWAMPListenPort: 862, LocalASN: 65342, - UnicastVrfs: []uint16{1}, + UnicastVrfs: []uint16{1, 2}, Config: cfg, AllTopologies: []TopologyModel{ { @@ -967,6 +967,7 @@ func TestRenderConfig(t *testing.T) { }, Tunnels: []*Tunnel{ { + // Tenant on unicast-default only → color 1 Id: 500, UnderlaySrcIP: net.IP{1, 1, 1, 1}, UnderlayDstIP: net.IP{2, 2, 2, 2}, @@ -979,12 +980,25 @@ func TestRenderConfig(t *testing.T) { TenantPubKey: "g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT", TenantTopologyColors: "color 1", }, + { + // Tenant on high-bandwidth only → color 2 + Id: 501, + UnderlaySrcIP: net.IP{1, 1, 1, 1}, + UnderlayDstIP: net.IP{3, 3, 3, 3}, + OverlaySrcIP: net.IP{169, 254, 1, 0}, + OverlayDstIP: net.IP{169, 254, 1, 1}, + DzIp: net.IP{100, 0, 0, 1}, + Allocated: true, + VrfId: 2, + TenantPubKey: "HWrkvPP3VErBAWCd4ELWGuh2mgx2Wx6cuNEA4X2SFpos", + TenantTopologyColors: "color 2", + }, }, }, } }(), - Want: "fixtures/flex_algo_multi_topology.tunnel.tmpl", - }, + Want: "fixtures/flex_algo_multi_topology.tunnel.tmpl", + }, { Name: "render_flex_algo_unicast_drained", Description: "render config with flex-algo enabled: one healthy WAN link and one draining WAN link — draining link appends UNICAST-DRAINED to admin-group", From 7a9d192e1c41000ff755320363494257bbb4832a Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Fri, 3 Apr 2026 00:47:18 -0500 Subject: [PATCH 06/11] controller: fix flex-algo template revert, Exclude constraint, Interface V3, and startup check - Emit no router traffic-engineering / no traffic-engineering / no next-hop resolution ribs when flex_algo.enabled is false (RFC-18 revert requirement) - Render flex-algo definition block for Exclude-constraint topologies - Fix internal Go SDK to correctly deserialize InterfaceV3 - Block flex-algo config push when any VPNv4 loopback is missing node-segment data instead of just logging --- .../fixtures/base.config.drained.txt | 3 +++ .../controller/fixtures/base.config.txt | 3 +++ .../fixtures/base.config.with.mgmt.vrf.txt | 3 +++ .../controller/fixtures/e2e.last.user.tmpl | 3 +++ .../controller/fixtures/e2e.multi.vrf.tmpl | 3 +++ .../controller/fixtures/e2e.peer.removal.tmpl | 3 +++ .../internal/controller/fixtures/e2e.tmpl | 3 +++ .../controller/fixtures/interfaces.txt | 3 +++ .../metro.routing.disabled.tunnel.tmpl | 3 +++ .../controller/fixtures/mixed.tunnel.tmpl | 3 +++ .../multi.vrf.mixed.metro.routing.tunnel.tmpl | 3 +++ .../controller/fixtures/multi.vrf.tunnel.tmpl | 3 +++ .../controller/fixtures/multicast.tunnel.tmpl | 3 +++ .../fixtures/nohardware.tunnel.tmpl | 3 +++ .../controller/fixtures/unicast.tunnel.tmpl | 3 +++ .../fixtures/unknown.peer.removal.tmpl | 3 +++ .../controller/internal/controller/models.go | 6 +++-- .../controller/internal/controller/server.go | 24 +++++++++++-------- .../internal/controller/templates/tunnel.tmpl | 15 ++++++++++++ .../sdk/go/serviceability/deserialize.go | 20 ++++++++++++++++ smartcontract/sdk/go/serviceability/state.go | 2 +- 21 files changed, 102 insertions(+), 13 deletions(-) diff --git a/controlplane/controller/internal/controller/fixtures/base.config.drained.txt b/controlplane/controller/internal/controller/fixtures/base.config.drained.txt index 068eca9658..a8d5ffebe1 100644 --- a/controlplane/controller/internal/controller/fixtures/base.config.drained.txt +++ b/controlplane/controller/internal/controller/fixtures/base.config.drained.txt @@ -121,6 +121,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate + no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -137,8 +138,10 @@ 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.txt b/controlplane/controller/internal/controller/fixtures/base.config.txt index 0319bb29fd..95653ca781 100644 --- a/controlplane/controller/internal/controller/fixtures/base.config.txt +++ b/controlplane/controller/internal/controller/fixtures/base.config.txt @@ -121,6 +121,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate + no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -137,8 +138,10 @@ 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..e3cf2c1473 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -113,8 +114,10 @@ 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.last.user.tmpl b/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl index 90fda914ee..abaa219e00 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -114,8 +115,10 @@ 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..1a261fc4f9 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -161,8 +162,10 @@ 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..fe280988c9 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -163,8 +164,10 @@ 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..642cb4910b 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.tmpl @@ -175,6 +175,7 @@ router bgp 65342 neighbor 169.254.0.3 activate ! address-family vpn-ipv4 + no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -200,8 +201,10 @@ 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/interfaces.txt b/controlplane/controller/internal/controller/fixtures/interfaces.txt index 27a3c7c8c7..8ec36f6089 100644 --- a/controlplane/controller/internal/controller/fixtures/interfaces.txt +++ b/controlplane/controller/internal/controller/fixtures/interfaces.txt @@ -179,6 +179,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 + no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -195,8 +196,10 @@ 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..459d83764f 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -142,8 +143,10 @@ 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..2963a5060b 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -199,8 +200,10 @@ 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..5c39acbd1e 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -149,8 +150,10 @@ 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..3a7c6666f7 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -149,8 +150,10 @@ 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..c0b5718608 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -176,8 +177,10 @@ 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..5b8d145df8 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -188,8 +189,10 @@ 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..e3a35534af 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -165,8 +166,10 @@ 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..f786ead148 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib ! vrf vrf1 rd 65342:1 @@ -168,8 +169,10 @@ 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 87813b792b..7ec9edf3c6 100644 --- a/controlplane/controller/internal/controller/models.go +++ b/controlplane/controller/internal/controller/models.go @@ -326,11 +326,13 @@ type templateData struct { Strings StringsHelper AllTopologies []TopologyModel Config *FeaturesConfig // nil when no features config is loaded + FlexAlgoBlocked bool // true when flex-algo is blocked due to missing loopback node-segment data } -// FlexAlgoEnabled returns true if a features config is loaded and flex_algo.enabled is set. +// FlexAlgoEnabled returns true if a features config is loaded, flex_algo.enabled is set, +// and flex-algo has not been blocked due to missing loopback node-segment data. func (d templateData) FlexAlgoEnabled() bool { - return d.Config != nil && d.Config.Features.FlexAlgo.Enabled + return d.Config != nil && d.Config.Features.FlexAlgo.Enabled && !d.FlexAlgoBlocked } // TopologyModel holds pre-computed topology data for template rendering. diff --git a/controlplane/controller/internal/controller/server.go b/controlplane/controller/internal/controller/server.go index 655ca3000d..2fff4794bb 100644 --- a/controlplane/controller/internal/controller/server.go +++ b/controlplane/controller/internal/controller/server.go @@ -50,14 +50,15 @@ type ServiceabilityProgramClient interface { } type stateCache struct { - Config serviceability.Config - 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 + Config serviceability.Config + 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 + FlexAlgoBlocked bool // true when flex-algo is enabled but loopbacks are missing node-segment data } type Controller struct { @@ -611,7 +612,8 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } } - // Check for VPNv4 loopbacks missing flex-algo node-segment data when flex-algo is enabled + // Check for VPNv4 loopbacks missing flex-algo node-segment data when flex-algo is enabled. + // If any are found, block flex-algo config push for this cycle per RFC-18. if c.featuresConfig != nil && c.featuresConfig.Features.FlexAlgo.Enabled && len(cache.Topologies) > 0 { for devicePubKey, d := range cache.Devices { if len(d.DevicePathologies) > 0 { @@ -619,9 +621,10 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } 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", + c.log.Error("flex_algo.enabled=true but VPNv4 loopback has no flex_algo_node_segments — run 'doublezero-admin migrate' and restart; flex-algo config will not be pushed this cycle", "device_pubkey", devicePubKey, "interface", intf.Name) + cache.FlexAlgoBlocked = true } } } @@ -925,6 +928,7 @@ func (c *Controller) GetConfig(ctx context.Context, req *pb.ConfigRequest) (*pb. Strings: StringsHelper{}, AllTopologies: allTopologies, Config: c.featuresConfig, + FlexAlgoBlocked: c.cache.FlexAlgoBlocked, } config, err := renderConfig(data) diff --git a/controlplane/controller/internal/controller/templates/tunnel.tmpl b/controlplane/controller/internal/controller/templates/tunnel.tmpl index 4bff223206..c32adb1983 100644 --- a/controlplane/controller/internal/controller/templates/tunnel.tmpl +++ b/controlplane/controller/internal/controller/templates/tunnel.tmpl @@ -264,6 +264,8 @@ router bgp 65342 {{- 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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib {{- end }} ! {{- range $vrfId := .UnicastVrfs }} @@ -306,11 +308,17 @@ router isis 1 {{- range $.AllTopologies }} flex-algo {{ .Name }} level-2 advertised {{- end }} + {{- else }} + {{- range $.AllTopologies }} + no flex-algo {{ .Name }} level-2 advertised + {{- end }} {{- end }} {{- if $.FlexAlgoEnabled }} traffic-engineering no shutdown is-type level-2 + {{- else }} + no traffic-engineering {{- end }} {{- if $.Device.Status.IsDrained }} set-overload-bit @@ -332,9 +340,16 @@ router traffic-engineering administrative-group include any {{ .AdminGroupBit }} exclude 1 color {{ .Color }} ! + {{- else if eq .ConstraintStr "exclude" }} + flex-algo {{ .FlexAlgoNumber }} {{ .Name }} + administrative-group exclude {{ .AdminGroupBit }} + 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 diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index 07d82e28f0..59908f5318 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -90,6 +90,8 @@ func DeserializeInterface(reader *ByteReader, iface *Interface) { DeserializeInterfaceV1(reader, iface) case 1: // version 2 DeserializeInterfaceV2(reader, iface) + case 2: // version 3 + DeserializeInterfaceV3(reader, iface) } } @@ -120,6 +122,24 @@ func DeserializeInterfaceV2(reader *ByteReader, iface *Interface) { iface.IpNet = reader.ReadNetworkV4() iface.NodeSegmentIdx = reader.ReadU16() iface.UserTunnelEndpoint = (reader.ReadU8() != 0) +} + +func DeserializeInterfaceV3(reader *ByteReader, iface *Interface) { + iface.Status = InterfaceStatus(reader.ReadU8()) + iface.Name = reader.ReadString() + iface.InterfaceType = InterfaceType(reader.ReadU8()) + iface.InterfaceCYOA = InterfaceCYOA(reader.ReadU8()) + iface.InterfaceDIA = InterfaceDIA(reader.ReadU8()) + loopbackTypeByte := reader.ReadU8() + iface.LoopbackType = LoopbackType(loopbackTypeByte) + iface.Bandwidth = reader.ReadU64() + iface.Cir = reader.ReadU64() + iface.Mtu = reader.ReadU16() + iface.RoutingMode = RoutingMode(reader.ReadU8()) + iface.VlanId = reader.ReadU16() + iface.IpNet = reader.ReadNetworkV4() + iface.NodeSegmentIdx = reader.ReadU16() + iface.UserTunnelEndpoint = (reader.ReadU8() != 0) iface.FlexAlgoNodeSegments = reader.ReadFlexAlgoNodeSegmentSlice() } diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index a9605b4fa7..5db0d478da 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -398,7 +398,7 @@ func (i Interface) MarshalJSON() ([]byte, error) { return json.Marshal(jsonIface) } -const CurrentInterfaceVersion = 2 +const CurrentInterfaceVersion = 3 type Device struct { AccountType AccountType From 4c981aa844face7d2c5784af6ad80cf0b66d356d Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Mon, 6 Apr 2026 14:15:05 -0500 Subject: [PATCH 07/11] controller: update fixtures, render tests, and tunnel template for RFC-18 flex-algo --- .../fixtures/base.config.drained.txt | 5 +- .../controller/fixtures/base.config.txt | 5 +- .../fixtures/base.config.with.mgmt.vrf.txt | 3 +- .../base.config.without.interfaces.peers.txt | 1 + .../controller/fixtures/e2e.last.user.tmpl | 3 +- .../controller/fixtures/e2e.multi.vrf.tmpl | 3 +- .../controller/fixtures/e2e.peer.removal.tmpl | 3 +- .../internal/controller/fixtures/e2e.tmpl | 9 +- .../e2e.without.interfaces.peers.tmpl | 1 + .../controller/fixtures/interfaces.txt | 13 +- .../metro.routing.disabled.tunnel.tmpl | 3 +- .../controller/fixtures/mixed.tunnel.tmpl | 3 +- .../multi.vrf.mixed.metro.routing.tunnel.tmpl | 3 +- .../controller/fixtures/multi.vrf.tunnel.tmpl | 3 +- .../controller/fixtures/multicast.tunnel.tmpl | 3 +- .../fixtures/nohardware.tunnel.tmpl | 3 +- .../controller/fixtures/unicast.tunnel.tmpl | 3 +- .../fixtures/unknown.peer.removal.tmpl | 3 +- .../internal/controller/render_test.go | 575 +++++++----------- .../controller/internal/controller/server.go | 26 +- .../internal/controller/templates/tunnel.tmpl | 28 +- 21 files changed, 292 insertions(+), 407 deletions(-) diff --git a/controlplane/controller/internal/controller/fixtures/base.config.drained.txt b/controlplane/controller/internal/controller/fixtures/base.config.drained.txt index a8d5ffebe1..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,7 +123,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -142,6 +144,7 @@ router isis 1 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.txt b/controlplane/controller/internal/controller/fixtures/base.config.txt index 95653ca781..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,7 +123,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -142,6 +144,7 @@ router isis 1 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 e3cf2c1473..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,7 +97,7 @@ router bgp 65342 ! address-family vpn-ipv4 neighbor 15.15.15.15 activate - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -118,6 +118,7 @@ router isis 1 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 abaa219e00..5fac0001e1 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.last.user.tmpl @@ -96,7 +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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -119,6 +119,7 @@ router isis 1 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 1a261fc4f9..db27913a5c 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.multi.vrf.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.multi.vrf.tmpl @@ -122,7 +122,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -166,6 +166,7 @@ router isis 1 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 fe280988c9..627625f547 100644 --- a/controlplane/controller/internal/controller/fixtures/e2e.peer.removal.tmpl +++ b/controlplane/controller/internal/controller/fixtures/e2e.peer.removal.tmpl @@ -136,7 +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 tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -168,6 +168,7 @@ router isis 1 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 642cb4910b..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,7 +181,7 @@ router bgp 65342 neighbor 169.254.0.3 activate ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -205,6 +211,7 @@ router isis 1 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 8ec36f6089..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,7 +189,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -200,6 +210,7 @@ router isis 1 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 459d83764f..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,7 +108,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -147,6 +147,7 @@ router isis 1 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 2963a5060b..dd5c7cc0c8 100644 --- a/controlplane/controller/internal/controller/fixtures/mixed.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/mixed.tunnel.tmpl @@ -174,7 +174,7 @@ router bgp 65342 neighbor 169.254.0.7 activate ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -204,6 +204,7 @@ router isis 1 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 5c39acbd1e..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,7 +110,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -154,6 +154,7 @@ router isis 1 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 3a7c6666f7..a4dbee85d2 100644 --- a/controlplane/controller/internal/controller/fixtures/multi.vrf.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/multi.vrf.tunnel.tmpl @@ -110,7 +110,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -154,6 +154,7 @@ router isis 1 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 c0b5718608..4429dc88e4 100644 --- a/controlplane/controller/internal/controller/fixtures/multicast.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/multicast.tunnel.tmpl @@ -160,7 +160,7 @@ router bgp 65342 neighbor 169.254.0.5 activate ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -181,6 +181,7 @@ router isis 1 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 5b8d145df8..057ed11d4d 100644 --- a/controlplane/controller/internal/controller/fixtures/nohardware.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/nohardware.tunnel.tmpl @@ -163,7 +163,7 @@ router bgp 65342 neighbor 169.254.0.7 activate ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -193,6 +193,7 @@ router isis 1 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 e3a35534af..5e15764866 100644 --- a/controlplane/controller/internal/controller/fixtures/unicast.tunnel.tmpl +++ b/controlplane/controller/internal/controller/fixtures/unicast.tunnel.tmpl @@ -122,7 +122,7 @@ router bgp 65342 address-family ipv4 ! address-family vpn-ipv4 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -170,6 +170,7 @@ router isis 1 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 f786ead148..7a6ad7f9e5 100644 --- a/controlplane/controller/internal/controller/fixtures/unknown.peer.removal.tmpl +++ b/controlplane/controller/internal/controller/fixtures/unknown.peer.removal.tmpl @@ -124,7 +124,7 @@ router bgp 65342 ! address-family vpn-ipv4 no neighbor 169.254.0.7 - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs ! vrf vrf1 rd 65342:1 @@ -173,6 +173,7 @@ router isis 1 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/render_test.go b/controlplane/controller/internal/controller/render_test.go index 1e1e7de7e7..1d5f77d9f0 100644 --- a/controlplane/controller/internal/controller/render_test.go +++ b/controlplane/controller/internal/controller/render_test.go @@ -1,7 +1,6 @@ package controller import ( - "flag" "net" "net/netip" "os" @@ -13,8 +12,6 @@ import ( "github.com/malbeclabs/doublezero/smartcontract/sdk/go/serviceability" ) -var update = flag.Bool("update", false, "update golden fixture files") - func TestRenderConfig(t *testing.T) { tests := []struct { Name string @@ -800,362 +797,6 @@ func TestRenderConfig(t *testing.T) { }, Want: "fixtures/multi.vrf.mixed.metro.routing.tunnel.tmpl", }, - { - Name: "render_flex_algo_enabled", - Description: "render config with flex-algo IS-IS topology config and BGP community stamping enabled", - Data: func() templateData { - cfg := &FeaturesConfig{} - cfg.Features.FlexAlgo.Enabled = true - cfg.Features.FlexAlgo.CommunityStamping.All = true - return templateData{ - Strings: StringsHelper{}, - MulticastGroupBlock: "239.0.0.0/24", - TelemetryTWAMPListenPort: 862, - LocalASN: 65342, - UnicastVrfs: []uint16{1}, - Config: cfg, - AllTopologies: []TopologyModel{ - { - Name: "unicast-default", - AdminGroupBit: 0, - FlexAlgoNumber: 128, - Color: 1, - 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 1", - }, - }, - }, - } - }(), - Want: "fixtures/flex_algo.tunnel.tmpl", - }, - { - Name: "render_flex_algo_multi_topology", - Description: "render config with two flex-algo topologies: three tunnels verify per-tunnel color stamping — unicast-default only (color 1), high-bandwidth only (color 2), and both (color 1 color 2)", - Data: func() templateData { - cfg := &FeaturesConfig{} - cfg.Features.FlexAlgo.Enabled = true - cfg.Features.FlexAlgo.CommunityStamping.All = true - return templateData{ - Strings: StringsHelper{}, - MulticastGroupBlock: "239.0.0.0/24", - TelemetryTWAMPListenPort: 862, - LocalASN: 65342, - UnicastVrfs: []uint16{1, 2}, - Config: cfg, - AllTopologies: []TopologyModel{ - { - Name: "unicast-default", - AdminGroupBit: 0, - FlexAlgoNumber: 128, - Color: 1, - ConstraintStr: "include-any", - }, - { - Name: "high-bandwidth", - AdminGroupBit: 2, - 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{ - { - // WAN link — unicast-default only - 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"}, - }, - { - // WAN link — unicast-default only (second peer) - Name: "Ethernet1/2", - Ip: netip.MustParsePrefix("172.16.1.2/31"), - Mtu: 2048, - InterfaceType: InterfaceTypePhysical, - Metric: 40000, - IsLink: true, - LinkStatus: serviceability.LinkStatusActivated, - LinkTopologies: []string{"unicast-default"}, - }, - { - // High-capacity WAN link — participates in both topologies - Name: "Ethernet2/1", - Ip: netip.MustParsePrefix("172.16.2.2/31"), - Mtu: 9000, - InterfaceType: InterfaceTypePhysical, - Metric: 10000, - IsLink: true, - LinkStatus: serviceability.LinkStatusActivated, - LinkTopologies: []string{"unicast-default", "high-bandwidth"}, - }, - { - // CYOA port — client-facing, not participating in flex-algo - Name: "Ethernet3", - Ip: netip.MustParsePrefix("10.0.0.1/24"), - Mtu: 9216, - InterfaceType: InterfaceTypePhysical, - IsCYOA: true, - }, - { - Name: "Loopback255", - Ip: netip.MustParsePrefix("14.14.14.14/32"), - NodeSegmentIdx: 100, - InterfaceType: InterfaceTypeLoopback, - LoopbackType: LoopbackTypeVpnv4, - FlexAlgoNodeSegments: []FlexAlgoNodeSegmentModel{ - {NodeSegmentIdx: 200, TopologyName: "unicast-default"}, - {NodeSegmentIdx: 201, TopologyName: "high-bandwidth"}, - }, - }, - }, - Tunnels: []*Tunnel{ - { - // Tenant on unicast-default only → color 1 - 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", - }, - { - // Tenant on high-bandwidth only → color 2 - Id: 501, - UnderlaySrcIP: net.IP{1, 1, 1, 1}, - UnderlayDstIP: net.IP{3, 3, 3, 3}, - OverlaySrcIP: net.IP{169, 254, 1, 0}, - OverlayDstIP: net.IP{169, 254, 1, 1}, - DzIp: net.IP{100, 0, 0, 1}, - Allocated: true, - VrfId: 2, - TenantPubKey: "HWrkvPP3VErBAWCd4ELWGuh2mgx2Wx6cuNEA4X2SFpos", - TenantTopologyColors: "color 2", - }, - }, - }, - } - }(), - Want: "fixtures/flex_algo_multi_topology.tunnel.tmpl", - }, - { - Name: "render_flex_algo_unicast_drained", - Description: "render config with flex-algo enabled: one healthy WAN link and one draining WAN link — draining link appends UNICAST-DRAINED to admin-group", - Data: func() templateData { - cfg := &FeaturesConfig{} - cfg.Features.FlexAlgo.Enabled = true - cfg.Features.FlexAlgo.CommunityStamping.All = true - return templateData{ - Strings: StringsHelper{}, - MulticastGroupBlock: "239.0.0.0/24", - TelemetryTWAMPListenPort: 862, - LocalASN: 65342, - UnicastVrfs: []uint16{1}, - Config: cfg, - AllTopologies: []TopologyModel{ - { - Name: "unicast-default", - AdminGroupBit: 0, - FlexAlgoNumber: 128, - Color: 1, - 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{ - { - // Healthy WAN link — normal admin-group - 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"}, - }, - { - // Draining WAN link — UNICAST-DRAINED appended - Name: "Ethernet1/2", - Ip: netip.MustParsePrefix("172.16.1.2/31"), - Mtu: 2048, - InterfaceType: InterfaceTypePhysical, - Metric: 40000, - IsLink: true, - LinkStatus: serviceability.LinkStatusActivated, - LinkTopologies: []string{"unicast-default"}, - UnicastDrained: true, - }, - { - 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 1", - }, - }, - }, - } - }(), - Want: "fixtures/flex_algo_unicast_drained.tunnel.tmpl", - }, - { - Name: "render_flex_algo_link_excluded", - Description: "render config with flex-algo enabled but a link excluded from tagging — renders no traffic-engineering administrative-group", - Data: func() templateData { - cfg := &FeaturesConfig{} - cfg.Features.FlexAlgo.Enabled = true - cfg.Features.FlexAlgo.LinkTagging.Exclude.Links = []string{"LinkPubKeyExcluded111111111111111111111111111"} - return templateData{ - Strings: StringsHelper{}, - MulticastGroupBlock: "239.0.0.0/24", - TelemetryTWAMPListenPort: 862, - LocalASN: 65342, - UnicastVrfs: []uint16{1}, - Config: cfg, - AllTopologies: []TopologyModel{ - { - Name: "unicast-default", - AdminGroupBit: 0, - FlexAlgoNumber: 128, - Color: 1, - 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"}, - PubKey: "LinkPubKeyExcluded111111111111111111111111111", - }, - { - 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, - }, - }, - }, - } - }(), - Want: "fixtures/flex_algo_link_excluded.tunnel.tmpl", - }, } for _, test := range tests { @@ -1164,12 +805,6 @@ func TestRenderConfig(t *testing.T) { if err != nil { t.Fatalf("error rendering template: %v", err) } - if *update { - if err := os.WriteFile(test.Want, []byte(got), 0o644); err != nil { - t.Fatalf("error writing fixture %s: %v", test.Want, err) - } - return - } var want []byte if strings.HasSuffix(test.Want, ".tmpl") { templateData := map[string]int{ @@ -1195,3 +830,213 @@ func TestRenderConfig(t *testing.T) { } } +func TestRenderFlexAlgoEnabled(t *testing.T) { + cfg := &FeaturesConfig{} + cfg.Features.FlexAlgo.Enabled = true + cfg.Features.FlexAlgo.CommunityStamping.All = true + + data := templateData{ + Strings: StringsHelper{}, + MulticastGroupBlock: "239.0.0.0/24", + TelemetryTWAMPListenPort: 862, + LocalASN: 65342, + UnicastVrfs: []uint16{1}, + Config: 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", + }, + }, + }, + } + + got, err := renderConfig(data) + if err != nil { + t.Fatalf("error rendering template: %v", err) + } + + checks := []struct { + desc string + present bool + substr string + }{ + // Change 1: interface admin-group (uppercase) + {"traffic-engineering enable on interface", true, " traffic-engineering\n traffic-engineering administrative-group UNICAST-DEFAULT"}, + // Change 3: BGP next-hop + {"next-hop resolution ribs in vpn-ipv4", true, "next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib"}, + // Change 4: IS-IS flex-algo advertisement inside segment-routing mpls + {"IS-IS flex-algo advertisement", true, "flex-algo unicast-default level-2 advertised"}, + {"IS-IS traffic-engineering block", true, " traffic-engineering\n no shutdown\n is-type level-2"}, + // Change 5: router traffic-engineering block (correct structure) + {"router traffic-engineering block", true, "router traffic-engineering"}, + {"segment-routing rib in TE block", true, " segment-routing\n rib system-colored-tunnel-rib"}, + {"router-id in TE block", true, " router-id ipv4 14.14.14.14"}, + {"UNICAST-DRAINED alias first", true, " administrative-group alias UNICAST-DRAINED group 0"}, + {"UNICAST-DEFAULT alias uppercase", true, " administrative-group alias UNICAST-DEFAULT group 1"}, + {"flex-algo TE definition", true, " flex-algo 129 unicast-default"}, + {"flex-algo admin-group include-any", true, " administrative-group include any 1 exclude 0"}, + {"flex-algo color", true, " color 2"}, + // Community stamping + {"extcommunity color in route-map", true, "set extcommunity color 2"}, + // Change 2: loopback node-segments + {"loopback base node-segment", true, " node-segment ipv4 index 100"}, + {"loopback flex-algo node-segment", true, " node-segment ipv4 index 200 flex-algo unicast-default"}, + // Negative: wrong patterns must not appear + {"no metric-type igp", false, "metric-type igp"}, + {"no topology standard", false, "topology standard"}, + } + + for _, c := range checks { + t.Run(c.desc, func(t *testing.T) { + if strings.Contains(got, c.substr) != c.present { + if c.present { + t.Errorf("expected %q to be present in rendered config, but it was not", c.substr) + } else { + t.Errorf("expected %q to be absent from rendered config, but it was present", c.substr) + } + } + }) + } +} + +func TestRenderFlexAlgoDisabled(t *testing.T) { + // Config is nil — flex-algo blocks must not appear. + 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", + }, + }, + }, + } + + got, err := renderConfig(data) + if err != nil { + t.Fatalf("error rendering template: %v", err) + } + + absent := []string{ + // Positive (non-removal) forms must be absent; cleanup `no ...` forms are checked below. + " traffic-engineering administrative-group", + "router traffic-engineering\n router-id", + "administrative-group alias", + "next-hop resolution ribs tunnel-rib", + "set extcommunity color", + } + + for _, substr := range absent { + t.Run("absent: "+substr, func(t *testing.T) { + if strings.Contains(got, substr) { + t.Errorf("expected %q to be absent from rendered config (flex-algo disabled), but it was present", substr) + } + }) + } + + present := []string{ + "no router traffic-engineering", + "no next-hop resolution ribs", + "no traffic-engineering administrative-group", + "no traffic-engineering", + } + + for _, substr := range present { + t.Run("present: "+substr, func(t *testing.T) { + if !strings.Contains(got, substr) { + t.Errorf("expected %q to be present in rendered config (flex-algo disabled cleanup), but it was absent", substr) + } + }) + } +} diff --git a/controlplane/controller/internal/controller/server.go b/controlplane/controller/internal/controller/server.go index 2fff4794bb..cada842f36 100644 --- a/controlplane/controller/internal/controller/server.go +++ b/controlplane/controller/internal/controller/server.go @@ -50,15 +50,14 @@ type ServiceabilityProgramClient interface { } type stateCache struct { - Config serviceability.Config - 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 - FlexAlgoBlocked bool // true when flex-algo is enabled but loopbacks are missing node-segment data + Config serviceability.Config + 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 } type Controller struct { @@ -413,7 +412,7 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } d.Interfaces[i].IsLink = true d.Interfaces[i].LinkStatus = link.Status - d.Interfaces[i].UnicastDrained = link.UnicastDrained + d.Interfaces[i].UnicastDrained = link.LinkFlags&0x01 != 0 d.Interfaces[i].PubKey = base58.Encode(link.PubKey[:]) // Resolve topology names from link_topologies pubkeys @@ -612,8 +611,7 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } } - // Check for VPNv4 loopbacks missing flex-algo node-segment data when flex-algo is enabled. - // If any are found, block flex-algo config push for this cycle per RFC-18. + // 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 { @@ -621,10 +619,9 @@ func (c *Controller) updateStateCache(ctx context.Context) error { } 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; flex-algo config will not be pushed this cycle", + 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) - cache.FlexAlgoBlocked = true } } } @@ -928,7 +925,6 @@ func (c *Controller) GetConfig(ctx context.Context, req *pb.ConfigRequest) (*pb. Strings: StringsHelper{}, AllTopologies: allTopologies, Config: c.featuresConfig, - FlexAlgoBlocked: c.cache.FlexAlgoBlocked, } config, err := renderConfig(data) diff --git a/controlplane/controller/internal/controller/templates/tunnel.tmpl b/controlplane/controller/internal/controller/templates/tunnel.tmpl index c32adb1983..5df6c3cced 100644 --- a/controlplane/controller/internal/controller/templates/tunnel.tmpl +++ b/controlplane/controller/internal/controller/templates/tunnel.tmpl @@ -111,6 +111,10 @@ interface {{ .Name }} {{- 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 }} @@ -128,7 +132,8 @@ interface {{ .Name }} {{- end }} isis hello padding isis network point-to-point - {{- if and $.FlexAlgoEnabled .Ip.IsValid .IsPhysical .Metric .IsLink (not .IsSubInterfaceParent) (not .IsCYOA) (not .IsDIA) }} + {{- 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 }} @@ -137,6 +142,10 @@ interface {{ .Name }} {{- else }} no traffic-engineering administrative-group {{- end }} + {{- else }} + no traffic-engineering administrative-group + no traffic-engineering + {{- end }} {{- end }} {{- end }} ! @@ -265,7 +274,7 @@ router bgp 65342 {{- if $.FlexAlgoEnabled }} next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib {{- else }} - no next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib tunnel-rib system-tunnel-rib + no next-hop resolution ribs {{- end }} ! {{- range $vrfId := .UnicastVrfs }} @@ -310,7 +319,7 @@ router isis 1 {{- end }} {{- else }} {{- range $.AllTopologies }} - no flex-algo {{ .Name }} level-2 advertised + no flex-algo {{ .Name }} level-2 {{- end }} {{- end }} {{- if $.FlexAlgoEnabled }} @@ -329,7 +338,9 @@ router isis 1 {{- if $.FlexAlgoEnabled }} router traffic-engineering router-id ipv4 {{ $.Device.Vpn4vLoopbackIP }} - administrative-group alias UNICAST-DRAINED group 1 + 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 }} @@ -337,20 +348,15 @@ router traffic-engineering {{- range $.AllTopologies }} {{- if eq .ConstraintStr "include-any" }} flex-algo {{ .FlexAlgoNumber }} {{ .Name }} - administrative-group include any {{ .AdminGroupBit }} exclude 1 - color {{ .Color }} - ! - {{- else if eq .ConstraintStr "exclude" }} - flex-algo {{ .FlexAlgoNumber }} {{ .Name }} - administrative-group exclude {{ .AdminGroupBit }} + 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 }} From c17b53ba58f0eb6387bec2b37179fcd4e535ece8 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Mon, 6 Apr 2026 14:43:55 -0500 Subject: [PATCH 08/11] controller: remove dead flex_algo fixtures and unused FlexAlgoBlocked field --- .../controller/fixtures/flex_algo.tunnel.tmpl | 212 ------------ .../flex_algo_link_excluded.tunnel.tmpl | 209 ------------ .../flex_algo_multi_topology.tunnel.tmpl | 314 ------------------ .../flex_algo_unicast_drained.tunnel.tmpl | 227 ------------- .../controller/internal/controller/models.go | 6 +- 5 files changed, 2 insertions(+), 966 deletions(-) delete mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl delete mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo_link_excluded.tunnel.tmpl delete mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl delete mode 100644 controlplane/controller/internal/controller/fixtures/flex_algo_unicast_drained.tunnel.tmpl diff --git a/controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl deleted file mode 100644 index 2eb4c2b539..0000000000 --- a/controlplane/controller/internal/controller/fixtures/flex_algo.tunnel.tmpl +++ /dev/null @@ -1,212 +0,0 @@ -! -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 - administrative-group alias UNICAST-DRAINED group 1 - administrative-group alias UNICAST-DEFAULT group 0 - flex-algo - flex-algo 128 unicast-default - administrative-group include any 0 exclude 1 - color 1 - ! -! -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 1 -! -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/flex_algo_link_excluded.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_link_excluded.tunnel.tmpl deleted file mode 100644 index 6cb74a2ae0..0000000000 --- a/controlplane/controller/internal/controller/fixtures/flex_algo_link_excluded.tunnel.tmpl +++ /dev/null @@ -1,209 +0,0 @@ -! -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 - no traffic-engineering administrative-group -! -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 - administrative-group alias UNICAST-DRAINED group 1 - administrative-group alias UNICAST-DEFAULT group 0 - flex-algo - flex-algo 128 unicast-default - administrative-group include any 0 exclude 1 - color 1 - ! -! -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 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/flex_algo_multi_topology.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl deleted file mode 100644 index 7b2bd1739e..0000000000 --- a/controlplane/controller/internal/controller/fixtures/flex_algo_multi_topology.tunnel.tmpl +++ /dev/null @@ -1,314 +0,0 @@ -! -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 -vrf instance vrf2 -ip routing -ip routing vrf vrf1 -ip routing vrf vrf2 -! -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 Ethernet1/2 - mtu 2048 - no switchport - ip address 172.16.1.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 Ethernet2/1 - mtu 9000 - no switchport - ip address 172.16.2.2/31 - pim ipv4 sparse-mode - isis enable 1 - isis circuit-type level-2 - isis hello-interval 1 - isis metric 10000 - no isis passive - isis hello padding - isis network point-to-point - traffic-engineering - traffic-engineering administrative-group UNICAST-DEFAULT HIGH-BANDWIDTH -! -interface Ethernet3 - mtu 9216 - no switchport - ip address 10.0.0.1/24 -! -interface Loopback255 - ip address 14.14.14.14/32 - node-segment ipv4 index 100 - node-segment ipv4 index 200 flex-algo unicast-default - node-segment ipv4 index 201 flex-algo high-bandwidth - 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 -default interface Tunnel501 -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 -! -interface Tunnel501 - description USER-UCAST-501 - ip access-group SEC-USER-501-IN in - vrf vrf2 - mtu 9216 - ip address 169.254.1.0/31 - tunnel mode gre - tunnel source 1.1.1.1 - tunnel destination 3.3.3.3 - 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 - vrf vrf2 - rd 65342:2 - route-target import vpn-ipv4 65342:2 - route-target export vpn-ipv4 65342:2 - router-id 7.7.7.7 - no neighbor 169.254.1.1 - neighbor 169.254.1.1 remote-as 65000 - neighbor 169.254.1.1 local-as 65342 no-prepend replace-as - neighbor 169.254.1.1 passive - neighbor 169.254.1.1 description USER-501 - neighbor 169.254.1.1 route-map RM-USER-501-IN in - neighbor 169.254.1.1 route-map RM-USER-501-OUT out - neighbor 169.254.1.1 maximum-routes 1 - neighbor 169.254.1.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 - flex-algo high-bandwidth 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 - administrative-group alias UNICAST-DRAINED group 1 - administrative-group alias UNICAST-DEFAULT group 0 - administrative-group alias HIGH-BANDWIDTH group 2 - flex-algo - flex-algo 128 unicast-default - administrative-group include any 0 exclude 1 - color 1 - ! - flex-algo 129 high-bandwidth - administrative-group include any 2 exclude 1 - 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-501-OUT -route-map RM-USER-501-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 1 -! -no route-map RM-USER-501-IN -route-map RM-USER-501-IN permit 10 - match ip address prefix-list PL-USER-501 - 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 prefix-list PL-USER-501 -ip prefix-list PL-USER-501 seq 10 permit 100.0.0.1/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 SEC-USER-501-IN -ip access-list SEC-USER-501-IN - counters per-entry - !ICMP - permit icmp 169.254.1.1/32 any - !TRACEROUTE - permit udp 169.254.1.1/32 any range 33434 33534 - !Allow TTL Exceeded for traceroute return packets - permit icmp 169.254.1.1/32 any time-exceeded - !PERMIT BGP - permit tcp 169.254.1.1/32 169.254.1.0/32 eq 179 - !PERMIT USER DZ_IP as a source only - permit ip 100.0.0.1/32 any - deny ip any any -! -no ip access-list standard SEC-USER-MCAST-BOUNDARY-500-OUT -no ip access-list standard SEC-USER-MCAST-BOUNDARY-501-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/flex_algo_unicast_drained.tunnel.tmpl b/controlplane/controller/internal/controller/fixtures/flex_algo_unicast_drained.tunnel.tmpl deleted file mode 100644 index 2516d51d73..0000000000 --- a/controlplane/controller/internal/controller/fixtures/flex_algo_unicast_drained.tunnel.tmpl +++ /dev/null @@ -1,227 +0,0 @@ -! -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 Ethernet1/2 - mtu 2048 - no switchport - ip address 172.16.1.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 UNICAST-DRAINED -! -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 - administrative-group alias UNICAST-DRAINED group 1 - administrative-group alias UNICAST-DEFAULT group 0 - flex-algo - flex-algo 128 unicast-default - administrative-group include any 0 exclude 1 - color 1 - ! -! -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 1 -! -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/models.go b/controlplane/controller/internal/controller/models.go index 7ec9edf3c6..87813b792b 100644 --- a/controlplane/controller/internal/controller/models.go +++ b/controlplane/controller/internal/controller/models.go @@ -326,13 +326,11 @@ type templateData struct { Strings StringsHelper AllTopologies []TopologyModel Config *FeaturesConfig // nil when no features config is loaded - FlexAlgoBlocked bool // true when flex-algo is blocked due to missing loopback node-segment data } -// FlexAlgoEnabled returns true if a features config is loaded, flex_algo.enabled is set, -// and flex-algo has not been blocked due to missing loopback node-segment data. +// 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 && !d.FlexAlgoBlocked + return d.Config != nil && d.Config.Features.FlexAlgo.Enabled } // TopologyModel holds pre-computed topology data for template rendering. From bee8833e1990fe0e4057e37c2424ff0dff314811 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Mon, 6 Apr 2026 15:35:38 -0500 Subject: [PATCH 09/11] sdk/go: restore LinkFlags uint8 and fix test fixtures after rebase onto PR1 --- .../sdk/go/serviceability/bytereader.go | 4 --- .../sdk/go/serviceability/client_test.go | 3 --- .../sdk/go/serviceability/deserialize.go | 25 ++++++------------- smartcontract/sdk/go/serviceability/state.go | 2 +- .../sdk/go/serviceability/state_test.go | 8 +++--- 5 files changed, 13 insertions(+), 29 deletions(-) diff --git a/smartcontract/sdk/go/serviceability/bytereader.go b/smartcontract/sdk/go/serviceability/bytereader.go index 2232e838a5..441b36b7e3 100644 --- a/smartcontract/sdk/go/serviceability/bytereader.go +++ b/smartcontract/sdk/go/serviceability/bytereader.go @@ -44,10 +44,6 @@ func (br *ByteReader) ReadU8() uint8 { return val } -func (br *ByteReader) ReadBool() bool { - return br.ReadU8() != 0 -} - func (br *ByteReader) ReadU16() uint16 { if br.offset+2 > len(br.data) { return 0 diff --git a/smartcontract/sdk/go/serviceability/client_test.go b/smartcontract/sdk/go/serviceability/client_test.go index 7389a3949a..6fd01d4596 100644 --- a/smartcontract/sdk/go/serviceability/client_test.go +++ b/smartcontract/sdk/go/serviceability/client_test.go @@ -396,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], }, diff --git a/smartcontract/sdk/go/serviceability/deserialize.go b/smartcontract/sdk/go/serviceability/deserialize.go index 59908f5318..b444b8b257 100644 --- a/smartcontract/sdk/go/serviceability/deserialize.go +++ b/smartcontract/sdk/go/serviceability/deserialize.go @@ -122,25 +122,16 @@ func DeserializeInterfaceV2(reader *ByteReader, iface *Interface) { iface.IpNet = reader.ReadNetworkV4() iface.NodeSegmentIdx = reader.ReadU16() iface.UserTunnelEndpoint = (reader.ReadU8() != 0) + // flex_algo_node_segments was merged into V2 from the old V3. + // Old V2 accounts (written before this field existed) will have no trailing + // bytes — ReadFlexAlgoNodeSegmentSlice returns nil/empty in that case. + iface.FlexAlgoNodeSegments = reader.ReadFlexAlgoNodeSegmentSlice() } +// DeserializeInterfaceV3 handles legacy on-chain accounts written with +// discriminant 2 (the old V3). Their layout is identical to the current V2. func DeserializeInterfaceV3(reader *ByteReader, iface *Interface) { - iface.Status = InterfaceStatus(reader.ReadU8()) - iface.Name = reader.ReadString() - iface.InterfaceType = InterfaceType(reader.ReadU8()) - iface.InterfaceCYOA = InterfaceCYOA(reader.ReadU8()) - iface.InterfaceDIA = InterfaceDIA(reader.ReadU8()) - loopbackTypeByte := reader.ReadU8() - iface.LoopbackType = LoopbackType(loopbackTypeByte) - iface.Bandwidth = reader.ReadU64() - iface.Cir = reader.ReadU64() - iface.Mtu = reader.ReadU16() - iface.RoutingMode = RoutingMode(reader.ReadU8()) - iface.VlanId = reader.ReadU16() - iface.IpNet = reader.ReadNetworkV4() - iface.NodeSegmentIdx = reader.ReadU16() - iface.UserTunnelEndpoint = (reader.ReadU8() != 0) - iface.FlexAlgoNodeSegments = reader.ReadFlexAlgoNodeSegmentSlice() + DeserializeInterfaceV2(reader, iface) } func DeserializeDevice(reader *ByteReader, dev *Device) { @@ -207,7 +198,7 @@ func DeserializeLink(reader *ByteReader, link *Link) { link.LinkHealth = LinkHealth(reader.ReadU8()) link.LinkDesiredStatus = LinkDesiredStatus(reader.ReadU8()) link.LinkTopologies = reader.ReadPubkeySlice() - link.UnicastDrained = reader.ReadBool() + link.LinkFlags = reader.ReadU8() } func DeserializeUser(reader *ByteReader, user *User) { diff --git a/smartcontract/sdk/go/serviceability/state.go b/smartcontract/sdk/go/serviceability/state.go index 5db0d478da..2201269f36 100644 --- a/smartcontract/sdk/go/serviceability/state.go +++ b/smartcontract/sdk/go/serviceability/state.go @@ -619,7 +619,7 @@ type Link struct { LinkDesiredStatus LinkDesiredStatus `influx:"tag,link_desired_status"` PubKey [32]byte `influx:"tag,pubkey,pubkey"` LinkTopologies [][32]byte - UnicastDrained bool + LinkFlags uint8 } func (l Link) MarshalJSON() ([]byte, error) { diff --git a/smartcontract/sdk/go/serviceability/state_test.go b/smartcontract/sdk/go/serviceability/state_test.go index 1cfb11eab6..e247e2bf27 100644 --- a/smartcontract/sdk/go/serviceability/state_test.go +++ b/smartcontract/sdk/go/serviceability/state_test.go @@ -72,7 +72,7 @@ func TestCustomJSONMarshal(t *testing.T) { "SideZIfaceName": "Switch1/1/1", "DelayOverrideNs": 10, "LinkTopologies": null, - "UnicastDrained": false, + "LinkFlags": 0, "PubKey": "` + dummyPubKeyB58 + `" }`, expectErr: false, @@ -125,7 +125,7 @@ func TestCustomJSONMarshal(t *testing.T) { "SideZIfaceName": "Edge2/0/0", "DelayOverrideNs": 0, "LinkTopologies": null, - "UnicastDrained": false, + "LinkFlags": 0, "PubKey": "` + dummyPubKeyB58 + `" }`, expectErr: false, @@ -158,7 +158,7 @@ func TestCustomJSONMarshal(t *testing.T) { "SideZIfaceName": "", "DelayOverrideNs": 0, "LinkTopologies": null, - "UnicastDrained": false, + "LinkFlags": 0, "PubKey": "11111111111111111111111111111111" }`, expectErr: false, @@ -310,7 +310,7 @@ func TestCustomJSONMarshal(t *testing.T) { "MgmtVrf": "mgmt-vrf", "Interfaces": [ { - "Version": 1, + "Version": 2, "Status": "activated", "Name": "Switch1/1/1", "InterfaceType": "physical", From e67b509cb72e2da511f91fca718ff81c199ada53 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Mon, 6 Apr 2026 16:20:23 -0500 Subject: [PATCH 10/11] controller: replace flex-algo string-check tests with golden fixtures --- .../base.config.flex-algo-disabled.txt | 195 ++++++++++ .../fixtures/base.config.flex-algo.txt | 214 +++++++++++ .../internal/controller/render_test.go | 341 +++++++----------- 3 files changed, 539 insertions(+), 211 deletions(-) create mode 100644 controlplane/controller/internal/controller/fixtures/base.config.flex-algo-disabled.txt create mode 100644 controlplane/controller/internal/controller/fixtures/base.config.flex-algo.txt 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/render_test.go b/controlplane/controller/internal/controller/render_test.go index 1d5f77d9f0..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 { @@ -829,214 +959,3 @@ func TestRenderConfig(t *testing.T) { }) } } - -func TestRenderFlexAlgoEnabled(t *testing.T) { - cfg := &FeaturesConfig{} - cfg.Features.FlexAlgo.Enabled = true - cfg.Features.FlexAlgo.CommunityStamping.All = true - - data := templateData{ - Strings: StringsHelper{}, - MulticastGroupBlock: "239.0.0.0/24", - TelemetryTWAMPListenPort: 862, - LocalASN: 65342, - UnicastVrfs: []uint16{1}, - Config: 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", - }, - }, - }, - } - - got, err := renderConfig(data) - if err != nil { - t.Fatalf("error rendering template: %v", err) - } - - checks := []struct { - desc string - present bool - substr string - }{ - // Change 1: interface admin-group (uppercase) - {"traffic-engineering enable on interface", true, " traffic-engineering\n traffic-engineering administrative-group UNICAST-DEFAULT"}, - // Change 3: BGP next-hop - {"next-hop resolution ribs in vpn-ipv4", true, "next-hop resolution ribs tunnel-rib colored system-colored-tunnel-rib"}, - // Change 4: IS-IS flex-algo advertisement inside segment-routing mpls - {"IS-IS flex-algo advertisement", true, "flex-algo unicast-default level-2 advertised"}, - {"IS-IS traffic-engineering block", true, " traffic-engineering\n no shutdown\n is-type level-2"}, - // Change 5: router traffic-engineering block (correct structure) - {"router traffic-engineering block", true, "router traffic-engineering"}, - {"segment-routing rib in TE block", true, " segment-routing\n rib system-colored-tunnel-rib"}, - {"router-id in TE block", true, " router-id ipv4 14.14.14.14"}, - {"UNICAST-DRAINED alias first", true, " administrative-group alias UNICAST-DRAINED group 0"}, - {"UNICAST-DEFAULT alias uppercase", true, " administrative-group alias UNICAST-DEFAULT group 1"}, - {"flex-algo TE definition", true, " flex-algo 129 unicast-default"}, - {"flex-algo admin-group include-any", true, " administrative-group include any 1 exclude 0"}, - {"flex-algo color", true, " color 2"}, - // Community stamping - {"extcommunity color in route-map", true, "set extcommunity color 2"}, - // Change 2: loopback node-segments - {"loopback base node-segment", true, " node-segment ipv4 index 100"}, - {"loopback flex-algo node-segment", true, " node-segment ipv4 index 200 flex-algo unicast-default"}, - // Negative: wrong patterns must not appear - {"no metric-type igp", false, "metric-type igp"}, - {"no topology standard", false, "topology standard"}, - } - - for _, c := range checks { - t.Run(c.desc, func(t *testing.T) { - if strings.Contains(got, c.substr) != c.present { - if c.present { - t.Errorf("expected %q to be present in rendered config, but it was not", c.substr) - } else { - t.Errorf("expected %q to be absent from rendered config, but it was present", c.substr) - } - } - }) - } -} - -func TestRenderFlexAlgoDisabled(t *testing.T) { - // Config is nil — flex-algo blocks must not appear. - 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", - }, - }, - }, - } - - got, err := renderConfig(data) - if err != nil { - t.Fatalf("error rendering template: %v", err) - } - - absent := []string{ - // Positive (non-removal) forms must be absent; cleanup `no ...` forms are checked below. - " traffic-engineering administrative-group", - "router traffic-engineering\n router-id", - "administrative-group alias", - "next-hop resolution ribs tunnel-rib", - "set extcommunity color", - } - - for _, substr := range absent { - t.Run("absent: "+substr, func(t *testing.T) { - if strings.Contains(got, substr) { - t.Errorf("expected %q to be absent from rendered config (flex-algo disabled), but it was present", substr) - } - }) - } - - present := []string{ - "no router traffic-engineering", - "no next-hop resolution ribs", - "no traffic-engineering administrative-group", - "no traffic-engineering", - } - - for _, substr := range present { - t.Run("present: "+substr, func(t *testing.T) { - if !strings.Contains(got, substr) { - t.Errorf("expected %q to be present in rendered config (flex-algo disabled cleanup), but it was absent", substr) - } - }) - } -} From 25fe2c9e253cf52b0788cea3cdee8747172f9111 Mon Sep 17 00:00:00 2001 From: Ben Cairns Date: Mon, 6 Apr 2026 23:06:09 -0500 Subject: [PATCH 11/11] controller: add RFC-18 flex-algo CHANGELOG entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) 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