Files
akvorado/outlet/metadata/provider/gnmi/srlinux_test.go
Vincent Bernat e20645c92e outlet/metadata: synchronous fetching of metadata
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.
2025-07-27 21:44:28 +02:00

849 lines
28 KiB
Go

// SPDX-FileCopyrightText: 2023 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package gnmi
import (
"context"
"fmt"
"net"
"net/netip"
"reflect"
"sort"
"strconv"
"strings"
"testing"
"time"
"akvorado/common/helpers"
"akvorado/common/reporter"
"akvorado/outlet/metadata/provider"
"github.com/openconfig/gnmi/proto/gnmi"
"github.com/openconfig/gnmic/pkg/api"
"github.com/scrapli/scrapligo/driver/network"
"github.com/scrapli/scrapligo/driver/options"
"github.com/scrapli/scrapligo/logging"
"github.com/scrapli/scrapligo/platform"
"github.com/scrapli/scrapligo/transport"
)
func waitSRLManagementServerReady(t *testing.T, d *network.Driver) {
const (
mgmtServerRdyCmd = "info from state system app-management application mgmt_server state | grep running"
readyForConfigCmd = "file cat /etc/opt/srlinux/devices/app_ephemeral.mgmt_server.ready_for_config"
)
retryTimer := time.Second
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
for {
select {
case <-ctx.Done():
t.Fatal("SR Linux management server not ready in time")
return
default:
resp, err := d.SendCommand(mgmtServerRdyCmd)
if err != nil || resp.Failed != nil {
time.Sleep(retryTimer)
continue
}
if !strings.Contains(resp.Result, "running") {
t.Log("SR Linux did not start management server yet")
time.Sleep(retryTimer)
continue
}
resp, err = d.SendCommand(readyForConfigCmd)
if err != nil || resp.Failed != nil {
time.Sleep(retryTimer)
continue
}
if !strings.Contains(resp.Result, "loaded initial configuration") {
t.Log("SR Linux did not load configuration yet")
time.Sleep(retryTimer)
continue
}
t.Log("SR Linux ready")
return
}
}
}
func TestSRLinux(t *testing.T) {
const (
srLinuxUsername = "admin"
srLinuxPassword = "NokiaSrl1!"
)
// Connect to SR Linux container
srLinux := helpers.CheckExternalService(t, "SR Linux",
[]string{"srlinux:22", "127.0.0.1:57401"})
srLinuxHostname, srLinuxPortStr, _ := net.SplitHostPort(srLinux)
srLinuxPort, _ := strconv.Atoi(srLinuxPortStr)
t.Logf("SR Linux is listening at %s:%d", srLinuxHostname, srLinuxPort)
logger, err := logging.NewInstance(
logging.WithLogger(t.Log),
logging.WithLevel(logging.Critical), // can be changed if needed
)
if err != nil {
t.Fatalf("NewInstance() error:\n%+v", err)
}
plat, err := platform.NewPlatform(
"nokia_srl", srLinuxHostname,
options.WithPort(srLinuxPort),
options.WithAuthNoStrictKey(),
options.WithAuthUsername(srLinuxUsername),
options.WithAuthPassword(srLinuxPassword),
options.WithTransportType(transport.StandardTransport),
options.WithLogger(logger),
)
if err != nil {
t.Fatalf("NewPlatform() error:\n%+v", err)
}
driver, err := plat.GetNetworkDriver()
if err != nil {
t.Fatalf("GetNetworkDriver() error:\n%+v", err)
}
for remaining := 3; remaining >= 0; remaining++ {
err = driver.Open()
if err != nil {
if remaining == 0 {
t.Fatalf("Open() error:\n%+v", err)
}
time.Sleep(300 * time.Millisecond)
continue
}
break
}
defer driver.Close()
waitSRLManagementServerReady(t, driver)
resp, err := driver.SendCommand("show version")
if err != nil {
t.Fatalf("SendCommand(show version) error:\n%+v", err)
}
t.Logf(
"sent command '%s', output received (SendCommand):\n %s",
resp.Input,
resp.Result,
)
if resp.Failed != nil {
t.Fatalf("SendCommand(show version) error:\n%+v", resp.Failed)
}
// Initial configuration
resp, err = driver.SendConfig(`
load factory
/ system gnmi-server
set admin-state enable network-instance mgmt admin-state enable
commit now
`)
if err != nil {
t.Fatalf("SendConfig() error:\n%+v", err)
}
t.Logf(
"sent command '%s', output received (SendCommand):\n %s",
resp.Input,
resp.Result,
)
if resp.Failed != nil {
t.Fatalf("SendConfig() error:\n%+v", resp.Failed)
}
// gNMI setup
srLinuxGNMI := helpers.CheckExternalService(t, "SR Linux gNMI",
[]string{"srlinux:57400", "127.0.0.1:57400"})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tg, err := api.NewTarget(
api.Address(srLinuxGNMI),
api.Username(srLinuxUsername),
api.Password(srLinuxPassword),
api.Timeout(time.Second),
api.Insecure(true),
)
tg.Config.RetryTimer = 10 * time.Millisecond
if err != nil {
t.Fatalf("api.NewTarget() error:\n%+v", err)
}
err = tg.CreateGNMIClient(ctx)
if err != nil {
t.Fatalf("CreateGNMIClient() error:\n%+v", err)
}
defer tg.Close()
resetConfig := func(t *testing.T) {
t.Helper()
resp, err := driver.SendConfig(`
set / system name host-name "srlinux"
/ interface ethernet-1/1
set admin-state enable
set description "1st interface"
/ interface ethernet-1/2
set admin-state enable
set description "2nd interface"
set ethernet aggregate-id lag1
/ interface ethernet-1/3
set admin-state enable
set description "3rd interface"
set ethernet aggregate-id lag1
/ interface lag1
set admin-state enable
set description "lag interface"
set lag lag-type static
/ interface ethernet-1/4 subinterface 1
set admin-state enable
set description "4th interface"
commit now
`)
if err != nil {
t.Fatalf("SendConfig() error:\n%+v", err)
}
if resp.Failed != nil {
t.Fatalf("SendConfig() error:\n%+v", resp.Failed)
}
}
stopSubscription := func(t *testing.T, name string) {
t.Helper()
tg.StopSubscription(name)
subRspChan, subErrChan := tg.ReadSubscriptions()
endSubscription:
for {
select {
case resp := <-subRspChan:
t.Logf("Subscribe() extra response:\n%+v", resp)
case err := <-subErrChan:
t.Logf("Subscribe() error:\n%+v", err)
default:
break endSubscription
}
}
}
for _, encoding := range []string{"json", "json_ietf"} {
// Test a "once" subscription
t.Run(fmt.Sprintf("subscribe once %s", encoding), func(t *testing.T) {
resetConfig(t)
subscribeReq, err := api.NewSubscribeRequest(
api.Subscription(api.Path("/system/name/host-name")),
api.Subscription(api.Path("/interface/name")),
api.Subscription(api.Path("/interface/description")),
api.Subscription(api.Path("/interface/subinterface/name")),
api.Subscription(api.Path("/interface/subinterface/description")),
api.Subscription(api.Path("/interface/ethernet/port-speed")),
api.Subscription(api.Path("/interface/lag/lag-speed")),
api.SubscriptionListModeONCE(),
api.Encoding(encoding),
)
if err != nil {
t.Fatalf("NewSubscribeRequest() error:\n%+v", err)
}
subscribeResp, err := tg.SubscribeOnce(ctx, subscribeReq)
if err != nil {
t.Fatalf("SubscribeOnce() error:\n%+v", err)
}
got := subscribeResponsesToEvents(subscribeResp)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
expected := []event{
{"/interface/description", "name=ethernet-1/1", "1st interface"},
{"/interface/description", "name=ethernet-1/2", "2nd interface"},
{"/interface/description", "name=ethernet-1/3", "3rd interface"},
{"/interface/description", "name=lag1", "lag interface"},
{"/interface/ethernet/port-speed", "name=ethernet-1/1", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/10", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/11", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/12", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/13", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/14", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/15", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/16", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/17", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/18", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/19", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/2", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/20", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/21", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/22", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/23", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/24", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/25", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/26", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/27", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/28", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/29", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/3", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/30", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/31", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/32", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/33", "10G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/34", "10G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/4", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/5", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/6", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/7", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/8", "100G"},
{"/interface/ethernet/port-speed", "name=ethernet-1/9", "100G"},
{"/interface/ethernet/port-speed", "name=mgmt0", "1G"},
{"/interface/lag/lag-speed", "name=lag1", "0"},
// Despite being subscribed, we don't get these...
// {"/interface/name", "name=ethernet-1/1", "ethernet-1/1"},
// {"/interface/name", "name=ethernet-1/2", "ethernet-1/2"},
// {"/interface/name", "name=ethernet-1/3", "ethernet-1/3"},
// {"/interface/name", "name=lag1", "lag1"},
{"/interface/subinterface/description", "name=ethernet-1/4,index=1", "4th interface"},
{"/interface/subinterface/name", "name=ethernet-1/4,index=1", "ethernet-1/4.1"},
{"/interface/subinterface/name", "name=mgmt0,index=0", "mgmt0.0"},
{"/system/name/host-name", "", "srlinux"},
}
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Get() (-got, +want):\n%s", diff)
}
})
// Test a regular subscription with onchange
if t.Failed() {
return
}
t.Run(fmt.Sprintf("subscribe changes %s", encoding), func(t *testing.T) {
resetConfig(t)
subscribeReq, err := api.NewSubscribeRequest(
api.Subscription(api.Path("/system/name/host-name"), api.SubscriptionModeON_CHANGE()),
api.Subscription(api.Path("/interface/description"), api.SubscriptionModeON_CHANGE()),
api.Subscription(api.Path("/interface/subinterface/name"), api.SubscriptionModeON_CHANGE()),
api.SubscriptionListModeSTREAM(),
api.Encoding(encoding),
)
if err != nil {
t.Fatalf("NewSubscribeRequest() error:\n%+v", err)
}
subscriptionName := fmt.Sprintf("changes-%s", encoding)
go tg.Subscribe(ctx, subscribeReq, subscriptionName)
defer stopSubscription(t, subscriptionName)
subRspChan, subErrChan := tg.ReadSubscriptions()
// Wait for first set of answers
timer := time.NewTimer(time.Second)
responses := []*gnmi.SubscribeResponse{}
outer1:
for {
select {
case <-ctx.Done():
case <-timer.C:
t.Fatalf("Subscribe(): no sync response")
case resp := <-subRspChan:
if resp.SubscriptionName == subscriptionName {
switch resp.Response.Response.(type) {
case *gnmi.SubscribeResponse_Update:
responses = append(responses, resp.Response)
case *gnmi.SubscribeResponse_SyncResponse:
break outer1
default:
t.Fatalf("Subscribe(): unknown type: %v", reflect.TypeOf(resp.Response.Response))
}
}
case err := <-subErrChan:
if err.SubscriptionName == subscriptionName {
t.Fatalf("Subscribe() error:\n%+v", err)
}
}
}
got := subscribeResponsesToEvents(responses)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
expected := []event{
{"/interface/description", "name=ethernet-1/1", "1st interface"},
{"/interface/description", "name=ethernet-1/2", "2nd interface"},
{"/interface/description", "name=ethernet-1/3", "3rd interface"},
{"/interface/description", "name=lag1", "lag interface"},
{"/interface/subinterface/name", "name=ethernet-1/4,index=1", "ethernet-1/4.1"},
{"/interface/subinterface/name", "name=mgmt0,index=0", "mgmt0.0"},
{"/system/name/host-name", "", "srlinux"},
}
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Subscribe() initial sync (-got, +want):\n%s", diff)
}
// Change the configuration and check for a change.
resp, err = driver.SendConfig(`
set / system name host-name "srlinux-new"
/ interface ethernet-1/1
set description "1st interface new"
/ interface ethernet-1/4
delete subinterface 1
commit now
`)
if err != nil {
t.Fatalf("SendConfig() error:\n%+v", err)
}
if resp.Failed != nil {
t.Fatalf("SendConfig() error:\n%+v", resp.Failed)
}
responses = []*gnmi.SubscribeResponse{}
expected = []event{
{"/interface/description", "name=ethernet-1/1", "1st interface new"},
// They should happen but they do not...
// {"/interface/subinterface/description", "name=ethernet-1/4,index=1", ""},
// {"/interface/subinterface/name", "name=ethernet-1/4,index=1", ""},
{"/system/name/host-name", "", "srlinux-new"},
}
timer = time.NewTimer(time.Second)
outer2:
for {
select {
case <-ctx.Done():
case <-timer.C:
break outer2
case resp := <-subRspChan:
if resp.SubscriptionName == subscriptionName {
switch resp.Response.Response.(type) {
case *gnmi.SubscribeResponse_Update:
responses = append(responses, resp.Response)
default:
t.Fatalf("Subscribe(): unknown type: %v", reflect.TypeOf(resp.Response.Response))
}
}
case err := <-subErrChan:
if err.SubscriptionName == subscriptionName {
t.Fatalf("Subscribe() error:\n%+v", err)
}
}
got := subscribeResponsesToEvents(responses)
if len(got) >= len(expected) {
break
}
}
got = subscribeResponsesToEvents(responses)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Subscribe() after change (-got, +want):\n%s", diff)
}
// Test what happens when we disconnect
if encoding == "json" {
resp, err = driver.SendConfig(`
/ system gnmi-server admin-state enable network-instance mgmt
set admin-state disable
commit stay
set admin-state enable
commit now
`)
if err != nil {
t.Fatalf("SendConfig() error:\n%+v", err)
}
if resp.Failed != nil {
t.Fatalf("SendConfig() error:\n%+v", resp.Failed)
}
responses = []*gnmi.SubscribeResponse{}
errors := []string{}
timer = time.NewTimer(time.Second)
outer3:
for {
select {
case <-ctx.Done():
case <-timer.C:
t.Fatalf("Subscribe(): no sync response")
case resp := <-subRspChan:
if resp.SubscriptionName == subscriptionName {
switch resp.Response.Response.(type) {
case *gnmi.SubscribeResponse_Update:
responses = append(responses, resp.Response)
case *gnmi.SubscribeResponse_SyncResponse:
break outer3
default:
t.Fatalf("Subscribe(): unknown type: %v", reflect.TypeOf(resp.Response.Response))
}
}
case err := <-subErrChan:
if err.SubscriptionName == subscriptionName {
errors = append(errors, err.Err.Error())
}
}
}
got = subscribeResponsesToEvents(responses)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
expected := []event{
{"/interface/description", "name=ethernet-1/1", "1st interface new"},
{"/interface/description", "name=ethernet-1/2", "2nd interface"},
{"/interface/description", "name=ethernet-1/3", "3rd interface"},
{"/interface/description", "name=lag1", "lag interface"},
{"/interface/subinterface/name", "name=mgmt0,index=0", "mgmt0.0"},
{"/system/name/host-name", "", "srlinux-new"},
}
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Subscribe() after disconnect (-got, +want):\n%s", diff)
}
expectedErrors := []string{
"rpc error: code = Unavailable desc = Cancelling all calls",
"retrying in 10ms",
}
if diff := helpers.Diff(errors, expectedErrors); diff != "" {
t.Fatalf("Subscribe() errors after disconnect (-got, +want):\n%s", diff)
}
}
})
// Test a regular subscription with sampling
if t.Failed() {
return
}
t.Run(fmt.Sprintf("subscribe sampling %s", encoding), func(t *testing.T) {
resetConfig(t)
subscribeReq, err := api.NewSubscribeRequest(
api.Subscription(
api.Path("/system/name/host-name"),
api.SubscriptionModeSAMPLE(),
api.SampleInterval(time.Second)),
api.Subscription(
api.Path("/interface/description"),
api.SubscriptionModeSAMPLE(),
api.SampleInterval(time.Second)),
api.SubscriptionListModeSTREAM(),
api.Encoding(encoding),
)
if err != nil {
t.Fatalf("NewSubscribeRequest() error:\n%+v", err)
}
subscriptionName := fmt.Sprintf("sampling-%s", encoding)
go tg.Subscribe(ctx, subscribeReq, subscriptionName)
defer stopSubscription(t, subscriptionName)
subRspChan, subErrChan := tg.ReadSubscriptions()
// Wait for first set of answers
responses := []*gnmi.SubscribeResponse{}
timer := time.NewTimer(time.Second)
outer4:
for {
select {
case <-ctx.Done():
case <-timer.C:
t.Fatal("Subscribe(): no sync response")
case resp := <-subRspChan:
if resp.SubscriptionName == subscriptionName {
switch resp.Response.Response.(type) {
case *gnmi.SubscribeResponse_Update:
responses = append(responses, resp.Response)
case *gnmi.SubscribeResponse_SyncResponse:
break outer4
default:
t.Fatalf("Subscribe(): unknown type: %v", reflect.TypeOf(resp.Response.Response))
}
}
case err := <-subErrChan:
if err.SubscriptionName == subscriptionName {
t.Fatalf("Subscribe() error:\n%+v", err)
}
}
}
got := subscribeResponsesToEvents(responses)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
expected := []event{
{"/interface/description", "name=ethernet-1/1", "1st interface"},
{"/interface/description", "name=ethernet-1/2", "2nd interface"},
{"/interface/description", "name=ethernet-1/3", "3rd interface"},
{"/interface/description", "name=lag1", "lag interface"},
{"/system/name/host-name", "", "srlinux"},
}
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Subscribe() initial sync (-got, +want):\n%s", diff)
}
// Get a second batch
timer = time.NewTimer(1200 * time.Millisecond)
responses = []*gnmi.SubscribeResponse{}
outer5:
for {
select {
case <-ctx.Done():
case <-timer.C:
break outer5
case resp := <-subRspChan:
if resp.SubscriptionName == subscriptionName {
switch resp.Response.Response.(type) {
case *gnmi.SubscribeResponse_Update:
responses = append(responses, resp.Response)
default:
t.Fatalf("Subscribe(): unknown type: %v", reflect.TypeOf(resp.Response.Response))
}
}
case err := <-subErrChan:
if err.SubscriptionName == subscriptionName {
t.Fatalf("Subscribe() error:\n%+v", err)
}
}
if len(subscribeResponsesToEvents(responses)) >= len(expected) {
break
}
}
got = subscribeResponsesToEvents(responses)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Subscribe() after sampling (-got, +want):\n%s", diff)
}
})
// Test a regular subscription with polling
if t.Failed() {
return
}
t.Run(fmt.Sprintf("subscribe polling %s", encoding), func(t *testing.T) {
t.Skip("subscribe with polling does not work on SR Linux")
resetConfig(t)
subscribeReq, err := api.NewSubscribeRequest(
api.Subscription(api.Path("/system/name/host-name")),
api.Subscription(api.Path("/interface/description")),
api.SubscriptionListModePOLL(),
api.Encoding(encoding),
)
if err != nil {
t.Fatalf("NewSubscribeRequest() error:\n%+v", err)
}
subscriptionName := fmt.Sprintf("polling-%s", encoding)
go tg.Subscribe(ctx, subscribeReq, subscriptionName)
defer stopSubscription(t, subscriptionName)
subRspChan, subErrChan := tg.ReadSubscriptions()
expected := []event{
{"/interface/description", "name=ethernet-1/1", "1st interface"},
{"/interface/description", "name=ethernet-1/2", "2nd interface"},
{"/interface/description", "name=ethernet-1/3", "3rd interface"},
{"/interface/description", "name=lag1", "lag interface"},
{"/system/name/host-name", "", "srlinux"},
}
// Get a few batches
time.Sleep(100 * time.Millisecond)
for i := range 3 {
if err := tg.SubscribePoll(ctx, subscriptionName); err != nil {
t.Fatalf("SubscribePoll() error:\n%+v", err)
}
timer := time.NewTimer(time.Second)
responses := []*gnmi.SubscribeResponse{}
outer6:
for {
select {
case <-ctx.Done():
case <-timer.C:
break outer6
case resp := <-subRspChan:
if resp.SubscriptionName == subscriptionName {
switch resp.Response.Response.(type) {
case *gnmi.SubscribeResponse_Update:
responses = append(responses, resp.Response)
default:
t.Fatalf("Subscribe(): unknown type: %v", reflect.TypeOf(resp.Response.Response))
}
}
case err := <-subErrChan:
if err.SubscriptionName == subscriptionName {
t.Fatalf("Subscribe() error:\n%+v", err)
}
}
if len(subscribeResponsesToEvents(responses)) >= len(expected) {
break
}
}
got := subscribeResponsesToEvents(responses)
sort.Slice(got, func(i, j int) bool {
if got[i].Path != got[j].Path {
return got[i].Path < got[j].Path
}
return got[i].Keys < got[j].Keys
})
if diff := helpers.Diff(got, expected); diff != "" {
t.Fatalf("Subscribe() after polling %d (-got, +want):\n%s", i+1, diff)
}
}
})
}
// Test the provider
if t.Failed() {
return
}
t.Run("provider", func(t *testing.T) {
resetConfig(t)
got := []string{}
lo := netip.MustParseAddr("::ffff:127.0.0.1")
config := DefaultConfiguration()
configP := config.(Configuration)
configP.MinimalRefreshInterval = time.Second
configP.AuthenticationParameters = helpers.MustNewSubnetMap(map[string]AuthenticationParameter{
"::/0": {
Username: srLinuxUsername,
Password: srLinuxPassword,
Insecure: true,
},
})
configP.Targets = helpers.MustNewSubnetMap(map[string]netip.Addr{
"::/0": netip.MustParseAddrPort(srLinuxGNMI).Addr(),
})
configP.Ports = helpers.MustNewSubnetMap(map[string]uint16{
"::/0": netip.MustParseAddrPort(srLinuxGNMI).Port(),
})
formatUpdate := func(exporter netip.Addr, iface string, answer provider.Answer) string {
return fmt.Sprintf("%s %v %s %s %s %s %d",
exporter.Unmap().String(), answer.Found, answer.Exporter.Name,
iface, answer.Interface.Name, answer.Interface.Description, answer.Interface.Speed)
}
r := reporter.NewMock(t)
p, err := configP.New(r)
if err != nil {
t.Fatalf("New() error:\n%+v", err)
}
// We need the indexes
subscribeReq, err := api.NewSubscribeRequest(
api.Subscription(api.Path("/interface/ifindex")),
api.Subscription(api.Path("/interface/subinterface/ifindex")),
api.SubscriptionListModeONCE(),
api.EncodingJSON(),
)
if err != nil {
t.Fatalf("NewSubscribeRequest() error:\n%+v", err)
}
subscribeResp, err := tg.SubscribeOnce(ctx, subscribeReq)
if err != nil {
t.Fatalf("SubscribeOnce() error:\n%+v", err)
}
indexes := map[string]uint{}
for _, event := range subscribeResponsesToEvents(subscribeResp) {
index, err := strconv.ParseUint(event.Value, 10, 32)
if err != nil {
t.Fatalf("ParseUint(%q) error:\n%+v", event.Value, err)
}
indexes[event.Keys] = uint(index)
}
t.Logf("indexes: %v", indexes)
// Wait a bit
answer, _ := p.Query(context.Background(), provider.Query{ExporterIP: lo, IfIndex: indexes["name=ethernet-1/1"]})
got = append(got, formatUpdate(lo, "ethernet-1/1", answer))
answer, _ = p.Query(context.Background(), provider.Query{ExporterIP: lo, IfIndex: indexes["name=ethernet-1/2"]})
got = append(got, formatUpdate(lo, "ethernet-1/2", answer))
answer, _ = p.Query(context.Background(), provider.Query{ExporterIP: lo, IfIndex: indexes["name=lag1"]})
got = append(got, formatUpdate(lo, "lag1", answer))
answer, _ = p.Query(context.Background(), provider.Query{ExporterIP: lo, IfIndex: indexes["name=ethernet-1/3"]})
got = append(got, formatUpdate(lo, "ethernet-1/3", answer))
answer, _ = p.Query(context.Background(), provider.Query{ExporterIP: lo, IfIndex: 5})
got = append(got, formatUpdate(lo, "idx5", answer))
answer, _ = p.Query(context.Background(), provider.Query{ExporterIP: lo,
IfIndex: indexes["name=ethernet-1/4,index=1"]})
got = append(got, formatUpdate(lo, "ethernet-1/4,index=1", answer))
if diff := helpers.Diff(got, []string{
"127.0.0.1 true srlinux ethernet-1/1 ethernet-1/1 1st interface 100000",
"127.0.0.1 true srlinux ethernet-1/2 ethernet-1/2 2nd interface 100000",
"127.0.0.1 true srlinux lag1 lag1 lag interface 0",
"127.0.0.1 true srlinux ethernet-1/3 ethernet-1/3 3rd interface 100000",
"127.0.0.1 false idx5 0",
"127.0.0.1 true srlinux ethernet-1/4,index=1 ethernet-1/4.1 4th interface 100000",
}); diff != "" {
t.Fatalf("Query() (-got, +want):\n%s", diff)
}
gotMetrics := r.GetMetrics("akvorado_outlet_metadata_provider_gnmi_", "-collector_seconds")
expectedMetrics := map[string]string{
`collector_count`: "1",
`collector_ready_info{exporter="127.0.0.1"}`: "1",
`encoding_info{encoding="json_ietf",exporter="127.0.0.1"}`: "1",
`model_info{exporter="127.0.0.1",model="Nokia SR Linux"}`: "1",
`paths_count{exporter="127.0.0.1"}`: "82",
`updates_total{exporter="127.0.0.1"}`: "1",
}
if diff := helpers.Diff(gotMetrics, expectedMetrics); diff != "" {
t.Errorf("Metrics (-got, +want):\n%s", diff)
}
// Change configuration and check again
t.Log("modify and check again")
got = []string{}
resp, err = driver.SendConfig(`
/ interface ethernet-1/1
set description "1st interface new"
/ interface ethernet-1/4
delete subinterface 1
commit now
`)
if err != nil {
t.Fatalf("SendConfig() error:\n%+v", err)
}
if resp.Failed != nil {
t.Fatalf("SendConfig() error:\n%+v", resp.Failed)
}
time.Sleep(time.Second) // We should exceed the second now and next request will trigger a refresh
t.Log("start queries")
answer, _ = p.Query(context.Background(),
provider.Query{ExporterIP: lo, IfIndex: indexes["name=ethernet-1/1"]})
got = append(got, formatUpdate(lo, "ethernet-1/1", answer))
answer, _ = p.Query(context.Background(),
provider.Query{ExporterIP: lo, IfIndex: indexes["name=ethernet-1/4,index=1"]})
got = append(got, formatUpdate(lo, "ethernet-1/4,index=1", answer))
if diff := helpers.Diff(got, []string{
// Fresh value
"127.0.0.1 true srlinux ethernet-1/1 ethernet-1/1 1st interface new 100000",
// Removed value
"127.0.0.1 false ethernet-1/4,index=1 0",
}); diff != "" {
t.Fatalf("Query() (-got, +want):\n%s", diff)
}
gotMetrics = r.GetMetrics("akvorado_outlet_metadata_provider_gnmi_", "-collector_seconds")
expectedMetrics = map[string]string{
`collector_count`: "1",
`collector_ready_info{exporter="127.0.0.1"}`: "1",
`encoding_info{encoding="json_ietf",exporter="127.0.0.1"}`: "1",
`model_info{exporter="127.0.0.1",model="Nokia SR Linux"}`: "1",
`paths_count{exporter="127.0.0.1"}`: "79",
`updates_total{exporter="127.0.0.1"}`: "2",
}
if diff := helpers.Diff(gotMetrics, expectedMetrics); diff != "" {
t.Errorf("Metrics (-got, +want):\n%s", diff)
}
})
}