Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Each service under this section can have the following settings:
| timeout | Time in which the check command should complete. Afterwards it will be handled as if the check command failed. Defaults to **10s**, format following that of [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration). |
| fail | The amount of times the check command should fail before the service is considered to be down. Defaults to **1** |
| rise | The amount of times the check command should succeed before the service is considered to be up. Defaults to **1** |
| prefixes | Array of prefixes, mixed IPv4 and IPv6. At least 1 prefix is **required** per service |
| prefixes | Array of prefixes, mixed IPv4 and IPv6. When no prefixes are given, the resulting filter won't match any addresses but just return **true** or **false**. |

## **[prometheus]**

Expand Down
6 changes: 4 additions & 2 deletions birdwatcher/bird.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ func writeBirdConfig(filename string, prefixes PrefixCollection, compatBird213 b
return err
}

tmpl := template.Must(template.New("func").Funcs(tplFuncs).Parse(functionsTemplate))
tmpl := template.Must(template.New("func").
Funcs(tplFuncs).
Parse(functionsTemplate))

tplBody := struct {
Collections PrefixCollection
Expand All @@ -63,7 +65,7 @@ func writeBirdConfig(filename string, prefixes PrefixCollection, compatBird213 b
}

var buf bytes.Buffer
if err := tmpl.Execute(&buf, tplBody); err != nil {
if err = tmpl.Execute(&buf, tplBody); err != nil {
return err
}

Expand Down
70 changes: 56 additions & 14 deletions birdwatcher/bird_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,40 @@ import (
func TestWriteBirdConfig(t *testing.T) {
t.Parallel()

t.Run("empty config", func(t *testing.T) {
t.Run("filter down", func(t *testing.T) {
t.Parallel()

// open tempfile
tmpFile, err := os.CreateTemp(t.TempDir(), "bird_test")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

// no prefixes, service unhealthy
prefixes := make(PrefixCollection)
prefixes["match_route"] = NewPrefixSet("match_route")
prefixes["match_route"] = NewPrefixSet(&ServiceCheck{FunctionName: "match_route"})

// write bird config with empty prefix list
// write bird config with empty prefix list and unhealthy
err = writeBirdConfig(tmpFile.Name(), prefixes, false)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/config_empty")
fixture, err := os.ReadFile("testdata/bird/filter_down")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))

// switch to compat mode and do it again
err = writeBirdConfig(tmpFile.Name(), prefixes, true)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err = os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err = os.ReadFile("testdata/bird/filter_down_compat")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))
Expand All @@ -46,8 +60,9 @@ func TestWriteBirdConfig(t *testing.T) {
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

// one healthy prefix, service assumed healthy
prefixes := make(PrefixCollection)
prefixes["match_route"] = NewPrefixSet("match_route")
prefixes["match_route"] = NewPrefixSet(&ServiceCheck{FunctionName: "match_route"})

for _, pref := range []string{"1.2.3.4/32", "2.3.4.5/26", "3.4.5.6/24", "4.5.6.7/21"} {
_, prf, _ := net.ParseCIDR(pref)
Expand All @@ -62,37 +77,64 @@ func TestWriteBirdConfig(t *testing.T) {
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/config")
fixture, err := os.ReadFile("testdata/bird/filter_one_prefix")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))

// switch to compat mode and do it again
// write bird config to it
err = writeBirdConfig(tmpFile.Name(), prefixes, true)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err = os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err = os.ReadFile("testdata/bird/filter_one_prefix_compat")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))
})

t.Run("one prefix, compat", func(t *testing.T) {
t.Run("filter up", func(t *testing.T) {
t.Parallel()

// open tempfile
tmpFile, err := os.CreateTemp(t.TempDir(), "bird_test")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

// no healthy prefixes, service explicitly healthy
prefixes := make(PrefixCollection)
prefixes["match_route"] = NewPrefixSet(&ServiceCheck{
FunctionName: "match_route",
state: ServiceStateUp,
})

prefixes["other_function"] = NewPrefixSet("other_function")
for _, pref := range []string{"5.6.7.8/32", "6.7.8.9/26", "7.8.9.10/24"} {
_, prf, _ := net.ParseCIDR(pref)
prefixes["other_function"].Add(*prf)
}
// write bird config to it
err = writeBirdConfig(tmpFile.Name(), prefixes, false)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/filter_up")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))

// switch to compat mode and do it again
// write bird config to it
err = writeBirdConfig(tmpFile.Name(), prefixes, true)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
data, err = os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/config_compat")
fixture, err = os.ReadFile("testdata/bird/filter_up_compat")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))
Expand Down
4 changes: 0 additions & 4 deletions birdwatcher/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,6 @@ func validateService(s *ServiceCheck) error {
s.Rise = defaultServiceRise
}

if len(s.Prefixes) == 0 {
return fmt.Errorf("service %s has no prefixes set", s.name)
}

return nil
}

Expand Down
14 changes: 1 addition & 13 deletions birdwatcher/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,6 @@ func TestConfig(t *testing.T) {
}
})

// check for error for service with no prefixes
t.Run("service no prefixes", func(t *testing.T) {
t.Parallel()

err := ReadConfig(&Config{}, "testdata/config/service_noprefixes")
if assert.Error(t, err) {
assert.Regexp(t, regexp.MustCompile("^service .+ has no prefixes set"), err.Error())
}
})

// check for error for service with invalid prefix
t.Run("invalid prefix", func(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -106,9 +96,7 @@ func TestConfig(t *testing.T) {
assert.Equal(t, defaultServiceRise, testConf.Services["foo"].Rise)
assert.Equal(t, defaultServiceTimeout, testConf.Services["foo"].Timeout)

if assert.Len(t, testConf.Services["foo"].prefixes, 1) {
assert.Equal(t, "192.168.0.0/24", testConf.Services["foo"].prefixes[0].String())
}
assert.Len(t, testConf.Services["foo"].prefixes, 0)

// check GetServices result
svcs := testConf.GetServices()
Expand Down
21 changes: 10 additions & 11 deletions birdwatcher/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,6 @@ func (h *HealthCheck) Start(services []*ServiceCheck, ready chan<- bool, status
}
}

func (h *HealthCheck) didReloadBefore() bool {
return h.reloadedBefore
}

func (h *HealthCheck) handleAction(action *Action, status chan string) {
for _, p := range action.Prefixes {
switch action.State {
Expand Down Expand Up @@ -160,7 +156,7 @@ func (h *HealthCheck) applyConfig(config Config, prefixes PrefixCollection) erro
// if config did not change, we should still reload if we don't know the
// state of BIRD
if errors.Is(err, errConfigIdentical) {
if h.didReloadBefore() {
if h.reloadedBefore {
cLog.Warning("config did not change, not reloading")

return nil
Expand Down Expand Up @@ -216,28 +212,31 @@ func (h *HealthCheck) applyConfig(config Config, prefixes PrefixCollection) erro
}

func (h *HealthCheck) addPrefix(svc *ServiceCheck, prefix net.IPNet) {
h.ensurePrefixSet(svc.FunctionName)
h.ensurePrefixSet(svc)

h.prefixes[svc.FunctionName].Add(prefix)
prefixStateMetric.WithLabelValues(svc.Name(), prefix.String()).Set(1.0)
}

func (h *HealthCheck) removePrefix(svc *ServiceCheck, prefix net.IPNet) {
h.ensurePrefixSet(svc.FunctionName)
h.ensurePrefixSet(svc)

h.prefixes[svc.FunctionName].Remove(prefix)
prefixStateMetric.WithLabelValues(svc.Name(), prefix.String()).Set(0.0)
}

func (h *HealthCheck) ensurePrefixSet(functionName string) {
func (h *HealthCheck) ensurePrefixSet(svc *ServiceCheck) {
// make sure the top level map is prepared
if h.prefixes == nil {
h.prefixes = make(PrefixCollection)
}

// make sure a mapping for this function name exists
if _, found := h.prefixes[functionName]; !found {
h.prefixes[functionName] = NewPrefixSet(functionName)
// make sure a mapping for this function name exists and reflects the current
// health status of the service
if _, found := h.prefixes[svc.FunctionName]; found {
h.prefixes[svc.FunctionName].healthy = svc.IsUp()
} else {
h.prefixes[svc.FunctionName] = NewPrefixSet(svc)
}
}

Expand Down
18 changes: 0 additions & 18 deletions birdwatcher/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,6 @@ func TestHealthCheck_removePrefix(t *testing.T) {
assert.Empty(t, testutil.ToFloat64(prefixStateMetric.WithLabelValues("svc1", "1.2.3.0/24")))
}

func TestHealthCheckDidReloadBefore(t *testing.T) {
t.Parallel()

hc := NewHealthCheck(Config{})

// expect both to fail
assert.False(t, hc.didReloadBefore())

// should succeed now
hc.reloadedBefore = true
assert.True(t, hc.didReloadBefore())

hc.reloadedBefore = false

// expect to fail again
assert.False(t, hc.didReloadBefore())
}

func TestHealthCheck_handleAction(t *testing.T) {
t.Parallel()

Expand Down
12 changes: 10 additions & 2 deletions birdwatcher/prefixset.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ type PrefixCollection map[string]*PrefixSet
type PrefixSet struct {
prefixes []net.IPNet
functionName string
healthy bool
}

// NewPrefixSet returns a new prefixset with given function name
func NewPrefixSet(functionName string) *PrefixSet {
return &PrefixSet{functionName: functionName}
func NewPrefixSet(svc *ServiceCheck) *PrefixSet {
return &PrefixSet{
functionName: svc.FunctionName,
healthy: svc.IsUp(),
}
}

// FunctionName returns the function name
Expand All @@ -33,6 +37,10 @@ func (p PrefixSet) Prefixes() []net.IPNet {
return p.prefixes
}

func (p PrefixSet) Healthy() bool {
return p.healthy
}

// Add adds a prefix to the PrefixSet if it wasn't already in it
func (p *PrefixSet) Add(prefix net.IPNet) {
pLog := log.WithFields(log.Fields{
Expand Down
4 changes: 2 additions & 2 deletions birdwatcher/prefixset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func TestPrefixSet_Add(t *testing.T) {
t.Parallel()

p := NewPrefixSet("foobar")
p := NewPrefixSet(&ServiceCheck{FunctionName: "foobar"})
// should be empty
assert.Empty(t, p.prefixes)

Expand Down Expand Up @@ -44,7 +44,7 @@ func TestPrefixSet_Add(t *testing.T) {
func TestPrefixSet_Remove(t *testing.T) {
t.Parallel()

p := NewPrefixSet("foobar")
p := NewPrefixSet(&ServiceCheck{FunctionName: "foobar"})

// add some prefixes
prefixes := make([]net.IPNet, 4)
Expand Down
4 changes: 4 additions & 0 deletions birdwatcher/templates/functions.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ function {{.FunctionName}}(){{- if not $.CompatBird213 }} -> bool{{- end }}
{{.}}
{{- end }}
];
{{- else }}
{{- if .Healthy }}
return true;
{{- else }}
return false;
{{- end }}
{{- end }}
}
{{- end }}
9 changes: 0 additions & 9 deletions birdwatcher/testdata/bird/config_compat

This file was deleted.

5 changes: 5 additions & 0 deletions birdwatcher/testdata/bird/filter_down_compat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# DO NOT EDIT MANUALLY
function match_route()
{
return false;
}
10 changes: 10 additions & 0 deletions birdwatcher/testdata/bird/filter_one_prefix_compat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# DO NOT EDIT MANUALLY
function match_route()
{
return net ~ [
1.2.3.4/32,
2.3.4.0/26,
3.4.5.0/24,
4.5.0.0/21
];
}
5 changes: 5 additions & 0 deletions birdwatcher/testdata/bird/filter_up
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# DO NOT EDIT MANUALLY
function match_route() -> bool
{
return true;
}
5 changes: 5 additions & 0 deletions birdwatcher/testdata/bird/filter_up_compat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# DO NOT EDIT MANUALLY
function match_route()
{
return true;
}
1 change: 0 additions & 1 deletion birdwatcher/testdata/config/minimal
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[services]
[services."foo"]
command = "/usr/bin/true"
prefixes = ["192.168.0.0/24"]
3 changes: 0 additions & 3 deletions birdwatcher/testdata/config/service_noprefixes

This file was deleted.