Skip to content

Commit

Permalink
Merge pull request #140 from telekom/feature/cfg-hot-replace
Browse files Browse the repository at this point in the history
Add FRR config hot-replace feature
  • Loading branch information
chdxD1 authored Sep 20, 2024
2 parents 8e0d22f + 1f5aecc commit 28a37d8
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 10 deletions.
8 changes: 8 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ type Config struct {
BPFInterfaces []string `yaml:"bpfInterfaces"`
SkipVRFConfig []string `yaml:"skipVRFConfig"`
ServerASN int `yaml:"serverASN"`

Replacements []Replacement `yaml:"replacements"`
}

type VRFConfig struct {
VNI int `yaml:"vni"`
RT string `yaml:"rt"`
}

type Replacement struct {
Old string `yaml:"old"`
New string `yaml:"new"`
Regex bool `yaml:"regex"`
}

func LoadConfig() (*Config, error) {
config := &Config{}

Expand Down
25 changes: 20 additions & 5 deletions pkg/frr/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"regexp"

"github.com/telekom/das-schiff-network-operator/pkg/config"
"github.com/telekom/das-schiff-network-operator/pkg/healthcheck"
"github.com/telekom/das-schiff-network-operator/pkg/nl"
)
Expand Down Expand Up @@ -33,7 +34,7 @@ type templateConfig struct {
HostRouterID string
}

func (m *Manager) Configure(in Configuration, nm *nl.Manager) (bool, error) {
func (m *Manager) Configure(in Configuration, nm *nl.Manager, nwopCfg *config.Config) (bool, error) {
// Remove permit from VRF and only allow deny rules for mgmt VRFs
for i := range in.VRFs {
if in.VRFs[i].Name != m.mgmtVrf {
Expand All @@ -50,7 +51,7 @@ func (m *Manager) Configure(in Configuration, nm *nl.Manager) (bool, error) {
}
}

config, err := m.renderSubtemplates(in, nm)
frrConfig, err := m.renderSubtemplates(in, nm)
if err != nil {
return false, err
}
Expand All @@ -60,12 +61,13 @@ func (m *Manager) Configure(in Configuration, nm *nl.Manager) (bool, error) {
return false, fmt.Errorf("error reading configuration file: %w", err)
}

targetConfig, err := render(m.configTemplate, config)
targetConfig, err := render(m.configTemplate, frrConfig)
if err != nil {
return false, err
}

targetConfig = fixRouteTargetReload(targetConfig)
targetConfig = applyCfgReplacements(targetConfig, nwopCfg.Replacements)

if !bytes.Equal(currentConfig, targetConfig) {
err = os.WriteFile(m.ConfigPath, targetConfig, frrPermissions)
Expand Down Expand Up @@ -154,8 +156,8 @@ func (m *Manager) renderSubtemplates(in Configuration, nlManager *nl.Manager) (*

// fixRouteTargetReload is a workaround for FRR's inability to reload route-targets if they are configured in a single line.
// This function splits such lines into multiple lines, each containing a single route-target.
func fixRouteTargetReload(config []byte) []byte {
return rtLinesRe.ReplaceAllFunc(config, func(s []byte) []byte {
func fixRouteTargetReload(frrConfig []byte) []byte {
return rtLinesRe.ReplaceAllFunc(frrConfig, func(s []byte) []byte {
parts := rtPartsRe.FindSubmatch(s)
if parts == nil {
return s
Expand All @@ -172,3 +174,16 @@ func fixRouteTargetReload(config []byte) []byte {
return []byte(lines[:len(lines)-1])
})
}

// applyCfgReplacements replaces placeholders in the configuration with the actual values.
func applyCfgReplacements(frrConfig []byte, replacements []config.Replacement) []byte {
for _, replacement := range replacements {
if !replacement.Regex {
frrConfig = bytes.ReplaceAll(frrConfig, []byte(replacement.Old), []byte(replacement.New))
} else {
re := regexp.MustCompile(replacement.Old)
frrConfig = re.ReplaceAll(frrConfig, []byte(replacement.New))
}
}
return frrConfig
}
8 changes: 4 additions & 4 deletions pkg/frr/frr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ var _ = Describe("frr", func() {
It("return error if cannot get underlay IP", func() {
m := &Manager{}
nlMock.EXPECT().AddrList(gomock.Any(), gomock.Any()).Return(nil, errors.New("error listing addresses"))
_, err := m.Configure(Configuration{}, nl.NewManager(nlMock))
_, err := m.Configure(Configuration{}, nl.NewManager(nlMock), &config.Config{})
Expect(err).To(HaveOccurred())
})
It("return error if cannot node's name", func() {
Expand All @@ -148,7 +148,7 @@ var _ = Describe("frr", func() {
nlMock.EXPECT().AddrList(gomock.Any(), gomock.Any()).Return([]netlink.Addr{
{IPNet: netlink.NewIPNet(net.IPv4(0, 0, 0, 0))},
}, nil)
_, err := m.Configure(Configuration{}, nl.NewManager(nlMock))
_, err := m.Configure(Configuration{}, nl.NewManager(nlMock), &config.Config{})
Expect(err).To(HaveOccurred())

if isSet {
Expand All @@ -165,7 +165,7 @@ var _ = Describe("frr", func() {
nlMock.EXPECT().AddrList(gomock.Any(), gomock.Any()).Return([]netlink.Addr{
{IPNet: netlink.NewIPNet(net.IPv4(0, 0, 0, 0))},
}, nil)
_, err = m.Configure(Configuration{}, nl.NewManager(nlMock))
_, err = m.Configure(Configuration{}, nl.NewManager(nlMock), &config.Config{})
Expect(err).To(HaveOccurred())

if isSet {
Expand All @@ -189,7 +189,7 @@ var _ = Describe("frr", func() {
nlMock.EXPECT().AddrList(gomock.Any(), gomock.Any()).Return([]netlink.Addr{
{IPNet: netlink.NewIPNet(net.IPv4(0, 0, 0, 0))},
}, nil)
_, err = m.Configure(Configuration{}, nl.NewManager(nlMock))
_, err = m.Configure(Configuration{}, nl.NewManager(nlMock), &config.Config{})
Expect(err).To(HaveOccurred())

if isSet {
Expand Down
2 changes: 1 addition & 1 deletion pkg/reconciler/layer3.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func (r *reconcile) configureFRR(vrfConfigs []frr.VRFConfiguration, reloadTwice
changed, err := r.frrManager.Configure(frr.Configuration{
VRFs: vrfConfigs,
ASN: r.config.ServerASN,
}, r.netlinkManager)
}, r.netlinkManager, r.config)
if err != nil {
r.Logger.Error(err, "error updating FRR configuration")
return fmt.Errorf("error updating FRR configuration: %w", err)
Expand Down

0 comments on commit 28a37d8

Please sign in to comment.