mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
As we are not constrained by time that much in the outlet, we can simplify the fetching of metadata by doing it synchronously. We still keep the breaker design to avoid continously polling a source that is not responsive, so we still can loose some data if we are not able to poll metadata. We also keep the background cache refresh. We also introduce a grace time of 1 minute to avoid loosing data during start. For the static provider, we wait for the remote data sources to be ready. For the gNMI provider, there are target windows of availability during which the cached data can be polled. The SNMP provider is loosing its ability to coalesce requests.
239 lines
5.4 KiB
Go
239 lines
5.4 KiB
Go
// SPDX-FileCopyrightText: 2024 Free Mobile
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package static
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"akvorado/common/helpers"
|
|
"akvorado/common/remotedatasourcefetcher"
|
|
"akvorado/common/reporter"
|
|
"akvorado/outlet/metadata/provider"
|
|
)
|
|
|
|
func TestInitStaticExporters(t *testing.T) {
|
|
r := reporter.NewMock(t)
|
|
conf := Configuration{
|
|
Exporters: helpers.MustNewSubnetMap(map[string]ExporterConfiguration{
|
|
"::ffff:203.0.113.0/120": {
|
|
Exporter: provider.Exporter{
|
|
Name: "something",
|
|
},
|
|
Default: provider.Interface{
|
|
Name: "iface1",
|
|
Description: "description 1",
|
|
Speed: 10000,
|
|
},
|
|
},
|
|
}),
|
|
}
|
|
|
|
p := &Provider{
|
|
r: r,
|
|
exportersMap: map[string][]exporterInfo{},
|
|
}
|
|
p.exporters.Store(conf.Exporters)
|
|
|
|
expected := map[string][]exporterInfo{}
|
|
|
|
if diff := helpers.Diff(p.exportersMap, expected); diff != "" {
|
|
t.Fatalf("static provider (-got, +want):\n%s", diff)
|
|
}
|
|
|
|
expected["static"] = []exporterInfo{
|
|
{
|
|
ExporterSubnet: "203.0.113.0/24",
|
|
Exporter: provider.Exporter{
|
|
Name: "something",
|
|
},
|
|
Default: provider.Interface{
|
|
Name: "iface1",
|
|
Description: "description 1",
|
|
Speed: 10000,
|
|
},
|
|
Interfaces: []exporterInterface{},
|
|
},
|
|
}
|
|
p.initStaticExporters()
|
|
|
|
if diff := helpers.Diff(p.exportersMap, expected); diff != "" {
|
|
t.Fatalf("static provider (-got, +want):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestRemoteExporterSources(t *testing.T) {
|
|
|
|
// Mux to answer requests
|
|
ready := make(chan bool)
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/exporters.json", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
select {
|
|
case <-ready:
|
|
default:
|
|
w.WriteHeader(404)
|
|
return
|
|
}
|
|
w.Header().Add("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
w.Write([]byte(`
|
|
{
|
|
"exporters": [
|
|
{
|
|
"exportersubnet": "2001:db8:2::/48",
|
|
"name": "exporter1",
|
|
"default": {
|
|
"name": "default",
|
|
"description": "default",
|
|
"speed": 100
|
|
},
|
|
"interfaces": [
|
|
{
|
|
"ifindex": 1,
|
|
"name": "iface1",
|
|
"description": "foo:desc1",
|
|
"speed": 1000
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"exportersubnet": "10.0.0.1",
|
|
"name": "exporter2",
|
|
"default": {
|
|
"name": "default",
|
|
"description": "default",
|
|
"speed": 100
|
|
},
|
|
"interfaces": [
|
|
{
|
|
"ifindex": 2,
|
|
"name": "iface2",
|
|
"description": "foo:desc2",
|
|
"speed": 1000
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"exportersubnet": "10.0.0.1/32",
|
|
"name": "exporter3",
|
|
"default": {
|
|
"name": "default",
|
|
"description": "default",
|
|
"speed": 100
|
|
},
|
|
"interfaces": [
|
|
{
|
|
"ifindex": 3,
|
|
"name": "iface3",
|
|
"description": "foo:desc3",
|
|
"speed": 1000
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
`))
|
|
}))
|
|
|
|
// Setup an HTTP server to serve the JSON
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("Listen() error:\n%+v", err)
|
|
}
|
|
server := &http.Server{
|
|
Addr: listener.Addr().String(),
|
|
Handler: mux,
|
|
}
|
|
address := listener.Addr()
|
|
go server.Serve(listener)
|
|
defer server.Shutdown(context.Background())
|
|
|
|
r := reporter.NewMock(t)
|
|
config := Configuration{
|
|
Exporters: helpers.MustNewSubnetMap(map[string]ExporterConfiguration{
|
|
"2001:db8:1::/48": {
|
|
Exporter: provider.Exporter{
|
|
Name: "nodefault",
|
|
},
|
|
IfIndexes: map[uint]provider.Interface{
|
|
10: {
|
|
Name: "Gi10",
|
|
Description: "10th interface",
|
|
Speed: 1000,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
ExporterSourcesTimeout: 10 * time.Millisecond,
|
|
ExporterSources: map[string]remotedatasourcefetcher.RemoteDataSource{
|
|
"local": {
|
|
URL: fmt.Sprintf("http://%s/exporters.json", address),
|
|
Method: "GET",
|
|
Headers: map[string]string{
|
|
"X-Foo": "hello",
|
|
},
|
|
Timeout: 20 * time.Millisecond,
|
|
Interval: 100 * time.Millisecond,
|
|
Transform: remotedatasourcefetcher.MustParseTransformQuery(`
|
|
.exporters[]
|
|
`),
|
|
},
|
|
},
|
|
}
|
|
p, _ := config.New(r)
|
|
|
|
// Query when json is not ready yet, we should get a timeout
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
|
defer cancel()
|
|
answer1, err := p.Query(ctx, provider.Query{
|
|
ExporterIP: netip.MustParseAddr("2001:db8:1::10"),
|
|
IfIndex: 9,
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("Query() should have been in error:\n%+v", answer1)
|
|
}
|
|
if !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("Query() error:\n%+v", err)
|
|
}
|
|
|
|
close(ready)
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
gotMetrics := r.GetMetrics("akvorado_common_remotedatasourcefetcher_data_")
|
|
expectedMetrics := map[string]string{
|
|
`total{source="local",type="metadata"}`: "3",
|
|
}
|
|
if diff := helpers.Diff(gotMetrics, expectedMetrics); diff != "" {
|
|
t.Fatalf("Metrics (-got, +want):\n%s", diff)
|
|
}
|
|
|
|
// We now should be able to resolve our new exporter from remote source
|
|
got, _ := p.Query(context.Background(), provider.Query{
|
|
ExporterIP: netip.MustParseAddr("2001:db8:2::10"),
|
|
IfIndex: 1,
|
|
})
|
|
|
|
expected := provider.Answer{
|
|
Found: true,
|
|
Exporter: provider.Exporter{
|
|
Name: "exporter1",
|
|
},
|
|
Interface: provider.Interface{
|
|
Name: "iface1",
|
|
Description: "foo:desc1",
|
|
Speed: 1000,
|
|
},
|
|
}
|
|
|
|
if diff := helpers.Diff(got, expected); diff != "" {
|
|
t.Fatalf("static provider (-got, +want):\n%s", diff)
|
|
}
|
|
}
|