// 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) } }) }