diff --git a/pkg/networkservice/chains/endpoint/combine_test.go b/pkg/networkservice/chains/endpoint/combine_test.go index 5d854f560..f65012a23 100644 --- a/pkg/networkservice/chains/endpoint/combine_test.go +++ b/pkg/networkservice/chains/endpoint/combine_test.go @@ -1,5 +1,7 @@ // Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // +// Copyright (c) 2024 Cisco and/or its affiliates. +// // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,7 +90,7 @@ func testCombine(t *testing.T, mechanism *networkservice.Mechanism) { kernel.MECHANISM: servers[0], memif.MECHANISM: servers[1], }) - }, newTestEndpoint(ctx, kernel.MECHANISM), newTestEndpoint(ctx, memif.MECHANISM)) + }, newTestEndpoint(ctx, kernel.MECHANISM, kernel.MECHANISM), newTestEndpoint(ctx, memif.MECHANISM, memif.MECHANISM)) cc := startEndpoint(ctx, t, e) defer func() { _ = cc.Close() }() @@ -123,11 +125,11 @@ func testCombine(t *testing.T, mechanism *networkservice.Mechanism) { require.Equal(t, (&networkservice.ConnectionEvent{ Type: networkservice.ConnectionEventType_UPDATE, Connections: map[string]*networkservice.Connection{ - conn.GetNextPathSegment().GetId(): { - Id: conn.GetNextPathSegment().GetId(), + conn.GetCurrentPathSegment().GetId(): { + Id: conn.GetCurrentPathSegment().GetId(), Mechanism: conn.GetMechanism(), Path: &networkservice.Path{ - Index: 1, + Index: 0, PathSegments: conn.GetPath().GetPathSegments(), }, }, @@ -166,7 +168,7 @@ func TestSwitchEndpoint_InitialStateTransfer(t *testing.T) { kernel.MECHANISM: servers[0], memif.MECHANISM: servers[1], }) - }, newTestEndpoint(ctx, kernel.MECHANISM), newTestEndpoint(ctx, memif.MECHANISM)) + }, newTestEndpoint(ctx, kernel.MECHANISM, kernel.MECHANISM), newTestEndpoint(ctx, memif.MECHANISM, memif.MECHANISM)) cc := startEndpoint(ctx, t, e) defer func() { _ = cc.Close() }() @@ -195,11 +197,11 @@ func TestSwitchEndpoint_InitialStateTransfer(t *testing.T) { Connections: make(map[string]*networkservice.Connection), } for _, conn := range conns { - expectedEvent.Connections[conn.GetNextPathSegment().GetId()] = &networkservice.Connection{ - Id: conn.GetNextPathSegment().GetId(), + expectedEvent.Connections[conn.GetCurrentPathSegment().GetId()] = &networkservice.Connection{ + Id: conn.GetCurrentPathSegment().GetId(), Mechanism: conn.GetMechanism(), Path: &networkservice.Path{ - Index: 1, + Index: 0, PathSegments: conn.GetPath().GetPathSegments(), }, } @@ -227,13 +229,14 @@ func TestSwitchEndpoint_DuplicateEndpoints(t *testing.T) { monitorCtx, cancelMonitor := context.WithCancel(ctx) - duplicate := newTestEndpoint(monitorCtx, "duplicate") + duplicate1 := newTestEndpoint(monitorCtx, kernel.MECHANISM, "duplicate") + duplicate2 := newTestEndpoint(monitorCtx, memif.MECHANISM, "duplicate") e := endpoint.Combine(func(servers []networkservice.NetworkServiceServer) networkservice.NetworkServiceServer { return mechanisms.NewServer(map[string]networkservice.NetworkServiceServer{ kernel.MECHANISM: servers[0], memif.MECHANISM: servers[1], }) - }, duplicate, duplicate) + }, duplicate1, duplicate2) cc := startEndpoint(ctx, t, e) defer func() { _ = cc.Close() }() @@ -266,11 +269,11 @@ func TestSwitchEndpoint_DuplicateEndpoints(t *testing.T) { require.Equal(t, (&networkservice.ConnectionEvent{ Type: networkservice.ConnectionEventType_UPDATE, Connections: map[string]*networkservice.Connection{ - conn.GetNextPathSegment().GetId(): { - Id: conn.GetNextPathSegment().GetId(), + conn.GetCurrentPathSegment().GetId(): { + Id: conn.GetCurrentPathSegment().GetId(), Mechanism: conn.GetMechanism(), Path: &networkservice.Path{ - Index: 1, + Index: 0, PathSegments: conn.GetPath().GetPathSegments(), }, }, @@ -310,13 +313,15 @@ type testEndpoint struct { networkservice.MonitorConnectionServer } -func newTestEndpoint(ctx context.Context, name string) *testEndpoint { +func newTestEndpoint(ctx context.Context, mechanism, name string) *testEndpoint { e := new(testEndpoint) e.NetworkServiceServer = next.NewNetworkServiceServer( - updatepath.NewServer(name), begin.NewServer(), metadata.NewServer(), monitor.NewServer(ctx, &e.MonitorConnectionServer), + mechanisms.NewServer(map[string]networkservice.NetworkServiceServer{ + mechanism: updatepath.NewServer(name), + }), ) return e } diff --git a/pkg/networkservice/common/mechanisms/client.go b/pkg/networkservice/common/mechanisms/client.go index fc7ae7446..b6941daab 100644 --- a/pkg/networkservice/common/mechanisms/client.go +++ b/pkg/networkservice/common/mechanisms/client.go @@ -1,5 +1,7 @@ // Copyright (c) 2021 Doc.ai and/or its affiliates. // +// Copyright (c) 2024 Cisco and/or its affiliates. +// // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,9 +46,11 @@ func NewClient(mechanisms map[string]networkservice.NetworkServiceClient) networ } func (mc *mechanismsClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { - if request.GetConnection().GetMechanism() != nil { - srv, ok := mc.mechanisms[request.GetConnection().GetMechanism().GetType()] + mech := request.GetConnection().GetMechanism() + if mech != nil { + srv, ok := mc.mechanisms[mech.GetType()] if ok { + storeMetrics(request.GetConnection(), mech, true) return srv.Request(ctx, request, opts...) } return nil, errUnsupportedMech @@ -57,6 +61,8 @@ func (mc *mechanismsClient) Request(ctx context.Context, request *networkservice if ok { req := request.Clone() var resp *networkservice.Connection + storeMetrics(req.GetConnection(), mechanism, true) + resp, respErr := cm.Request(ctx, req, opts...) if respErr == nil { return resp, nil @@ -70,6 +76,7 @@ func (mc *mechanismsClient) Request(ctx context.Context, request *networkservice func (mc *mechanismsClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { c, ok := mc.mechanisms[conn.GetMechanism().GetType()] if ok { + storeMetrics(conn, conn.GetMechanism(), true) return c.Close(ctx, conn) } return nil, errCannotSupportMech diff --git a/pkg/networkservice/common/mechanisms/client_test.go b/pkg/networkservice/common/mechanisms/client_test.go index 4fc146448..4d4d0a9ac 100644 --- a/pkg/networkservice/common/mechanisms/client_test.go +++ b/pkg/networkservice/common/mechanisms/client_test.go @@ -1,5 +1,7 @@ // Copyright (c) 2021 Doc.ai and/or its affiliates. // +// Copyright (c) 2024 Cisco and/or its affiliates. +// // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +20,7 @@ package mechanisms_test import ( "context" + "fmt" "testing" "github.com/pkg/errors" @@ -178,3 +181,28 @@ func Test_Client_DontCallNextByItself(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 2, len(ch)) } + +const ( + ifnameKey = "name" + ifname = "nsm-1" +) + +func Test_Client_Metrics(t *testing.T) { + c := client() + metricsKey := "client_interface" + + for _, request := range permuteOverMechanismPreferenceOrder(request()) { + request.MechanismPreferences[0].Parameters[ifnameKey] = ifname + request.Connection.Path = &networkservice.Path{ + PathSegments: make([]*networkservice.PathSegment, 1), + Index: 0, + } + + conn, err := c.Request(context.Background(), request) + require.NoError(t, err) + require.NotNil(t, conn.Path) + require.Len(t, conn.Path.PathSegments, 1) + require.NotNil(t, conn.Path.PathSegments[0].Metrics) + require.Equal(t, fmt.Sprintf("%s/%s", request.MechanismPreferences[0].Type, ifname), conn.Path.PathSegments[0].Metrics[metricsKey]) + } +} diff --git a/pkg/networkservice/common/mechanisms/common.go b/pkg/networkservice/common/mechanisms/common.go index 4dbf7b8a3..b3852e142 100644 --- a/pkg/networkservice/common/mechanisms/common.go +++ b/pkg/networkservice/common/mechanisms/common.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2023 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,7 +16,67 @@ package mechanisms -import "github.com/pkg/errors" +import ( + "fmt" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/pkg/errors" +) var errCannotSupportMech = errors.New("cannot support any of the requested mechanism") var errUnsupportedMech = errors.New("unsupported mechanism") + +const ( + clientMetricKey = "client_interface" + serverMetricKey = "server_interface" + nameKey = "name" + unresolvedName = "unknown" +) + +type interfacesInfo struct { + interfaceName string + interfaceType string +} + +func (i *interfacesInfo) getInterfaceDetails() string { + return fmt.Sprintf("%s/%s", i.interfaceType, i.interfaceName) +} + +// Save interface details in Path +func storeMetrics(conn *networkservice.Connection, mechanism *networkservice.Mechanism, isClient bool) { + path := conn.GetPath() + if path == nil { + return + } + + segments := path.GetPathSegments() + if segments == nil { + return + } + + segment := segments[path.Index] + params := mechanism.GetParameters() + + name := unresolvedName + if params != nil { + name = params[nameKey] + } + + info := &interfacesInfo{ + interfaceName: name, + interfaceType: mechanism.GetType(), + } + + if segment.Metrics == nil { + segment.Metrics = make(map[string]string) + } + + metricKey := clientMetricKey + if !isClient { + metricKey = serverMetricKey + } + + if _, ok := segment.Metrics[metricKey]; !ok { + segment.Metrics[metricKey] = info.getInterfaceDetails() + } +} diff --git a/pkg/networkservice/common/mechanisms/server.go b/pkg/networkservice/common/mechanisms/server.go index 602a9e972..95ff60994 100644 --- a/pkg/networkservice/common/mechanisms/server.go +++ b/pkg/networkservice/common/mechanisms/server.go @@ -54,9 +54,11 @@ func NewServer(mechanisms map[string]networkservice.NetworkServiceServer) networ } func (ms *mechanismsServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { - if request.GetConnection().GetMechanism() != nil { - srv, ok := ms.mechanisms[request.GetConnection().GetMechanism().GetType()] + mech := request.GetConnection().GetMechanism() + if mech != nil { + srv, ok := ms.mechanisms[mech.GetType()] if ok { + storeMetrics(request.GetConnection(), mech, false) return srv.Request(ctx, request) } return nil, errors.WithStack(errUnsupportedMech) @@ -70,6 +72,7 @@ func (ms *mechanismsServer) Request(ctx context.Context, request *networkservice var resp *networkservice.Connection resp, respErr := srv.Request(ctx, req) if respErr == nil { + storeMetrics(resp, resp.GetMechanism(), false) return resp, nil } err = errors.Wrap(err, respErr.Error()) @@ -81,6 +84,7 @@ func (ms *mechanismsServer) Request(ctx context.Context, request *networkservice func (ms *mechanismsServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { srv, ok := ms.mechanisms[conn.GetMechanism().GetType()] if ok { + storeMetrics(conn, conn.GetMechanism(), false) return srv.Close(ctx, conn) } return nil, errCannotSupportMech diff --git a/pkg/networkservice/common/mechanisms/server_test.go b/pkg/networkservice/common/mechanisms/server_test.go index 8f447f871..8ce41a9c0 100644 --- a/pkg/networkservice/common/mechanisms/server_test.go +++ b/pkg/networkservice/common/mechanisms/server_test.go @@ -2,7 +2,7 @@ // // Copyright (c) 2021 Doc.ai and/or its affiliates. // -// Copyright (c) 2023 Cisco and/or its affiliates. +// Copyright (c) 2023-2024 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -22,6 +22,7 @@ package mechanisms_test import ( "context" + "fmt" "testing" "github.com/pkg/errors" @@ -62,20 +63,24 @@ func request() *networkservice.NetworkServiceRequest { Connection: &networkservice.Connection{}, MechanismPreferences: []*networkservice.Mechanism{ { - Cls: cls.LOCAL, - Type: memif.MECHANISM, + Cls: cls.LOCAL, + Type: memif.MECHANISM, + Parameters: make(map[string]string), }, { - Cls: cls.LOCAL, - Type: kernel.MECHANISM, + Cls: cls.LOCAL, + Type: kernel.MECHANISM, + Parameters: make(map[string]string), }, { - Cls: cls.REMOTE, - Type: srv6.MECHANISM, + Cls: cls.REMOTE, + Type: srv6.MECHANISM, + Parameters: make(map[string]string), }, { - Cls: cls.REMOTE, - Type: vxlan.MECHANISM, + Cls: cls.REMOTE, + Type: vxlan.MECHANISM, + Parameters: make(map[string]string), }, }, } @@ -239,3 +244,24 @@ func TestDontCallNextByItself(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 2, len(ch)) } + +func TestMetrics(t *testing.T) { + s := server() + metricsKey := "server_interface" + + for _, request := range permuteOverMechanismPreferenceOrder(request()) { + request.MechanismPreferences[0].Parameters[ifnameKey] = ifname + request.Connection.Path = &networkservice.Path{ + PathSegments: make([]*networkservice.PathSegment, 1), + Index: 0, + } + + conn, err := s.Request(context.Background(), request) + require.NoError(t, err) + require.NotNil(t, conn.Path) + require.Len(t, conn.Path.PathSegments, 1) + require.NotNil(t, conn.Path.PathSegments[0].Metrics) + + require.Equal(t, fmt.Sprintf("%s/%s", request.MechanismPreferences[0].Type, ifname), conn.Path.PathSegments[0].Metrics[metricsKey]) + } +}