diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e40bee8..ef49353b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -48,6 +48,10 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 + - name: Setup Go + uses: actions/setup-go@v1 + with: + go-version: 1.16 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: diff --git a/pkg/networkservice/chains/forwarder/options.go b/pkg/networkservice/chains/forwarder/options.go index f1bbd175..6ff43005 100644 --- a/pkg/networkservice/chains/forwarder/options.go +++ b/pkg/networkservice/chains/forwarder/options.go @@ -32,15 +32,16 @@ import ( ) type forwarderOptions struct { - name string - authorizeServer networkservice.NetworkServiceServer - clientURL *url.URL - dialTimeout time.Duration - domain2Device map[string]string - statsOpts []stats.Option - cleanupOpts []cleanup.Option - vxlanOpts []vxlan.Option - dialOpts []grpc.DialOption + name string + authorizeServer networkservice.NetworkServiceServer + clientURL *url.URL + dialTimeout time.Duration + dumpCleanupTimeout time.Duration + domain2Device map[string]string + statsOpts []stats.Option + cleanupOpts []cleanup.Option + vxlanOpts []vxlan.Option + dialOpts []grpc.DialOption } // Option is an option pattern for forwarder chain elements @@ -77,6 +78,13 @@ func WithDialTimeout(dialTimeout time.Duration) Option { } } +// WithDumpCleanupTimeout sets timeout to remove unused dumped interfaces +func WithDumpCleanupTimeout(timeout time.Duration) Option { + return func(o *forwarderOptions) { + o.dumpCleanupTimeout = timeout + } +} + // WithVlanDomain2Device sets vlan option func WithVlanDomain2Device(domain2Device map[string]string) Option { return func(o *forwarderOptions) { diff --git a/pkg/networkservice/chains/forwarder/server.go b/pkg/networkservice/chains/forwarder/server.go index 1d53416c..52f9faf9 100644 --- a/pkg/networkservice/chains/forwarder/server.go +++ b/pkg/networkservice/chains/forwarder/server.go @@ -63,6 +63,7 @@ import ( "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/tag" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/up" "github.com/networkservicemesh/sdk-vpp/pkg/networkservice/xconnect" + "github.com/networkservicemesh/sdk-vpp/pkg/tools/dumptool" ) // Connection aggregates the api.Connection and api.ChannelProvider interfaces @@ -78,15 +79,22 @@ type xconnectNSServer struct { // NewServer - returns an implementation of the xconnectns network service func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, vppConn Connection, tunnelIP net.IP, options ...Option) endpoint.Endpoint { opts := &forwarderOptions{ - name: "forwarder-vpp-" + uuid.New().String(), - authorizeServer: authorize.NewServer(authorize.Any()), - clientURL: &url.URL{Scheme: "unix", Host: "connect.to.socket"}, - dialTimeout: time.Millisecond * 200, - domain2Device: make(map[string]string), + name: "forwarder-vpp-" + uuid.New().String(), + authorizeServer: authorize.NewServer(authorize.Any()), + clientURL: &url.URL{Scheme: "unix", Host: "connect.to.socket"}, + dialTimeout: time.Millisecond * 200, + dumpCleanupTimeout: time.Minute * 10, + domain2Device: make(map[string]string), } for _, opt := range options { opt(opts) } + + dumpOption := &dumptool.DumpOption{ + PodName: opts.name, + Timeout: opts.dumpCleanupTimeout, + } + nseClient := registryclient.NewNetworkServiceEndpointRegistryClient(ctx, opts.clientURL, registryclient.WithNSEAdditionalFunctionality( registryrecvfd.NewNetworkServiceEndpointRegistryClient(), @@ -113,7 +121,7 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, vppConn memif.MECHANISM: memif.NewServer(ctx, vppConn, memif.WithDirectMemif(), memif.WithChangeNetNS()), - kernel.MECHANISM: kernel.NewServer(vppConn), + kernel.MECHANISM: kernel.NewServer(vppConn, kernel.WithDump(dumpOption)), vxlan.MECHANISM: vxlan.NewServer(vppConn, tunnelIP, opts.vxlanOpts...), wireguard.MECHANISM: wireguard.NewServer(vppConn, tunnelIP), }), @@ -136,7 +144,7 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, vppConn memif.NewClient(vppConn, memif.WithChangeNetNS(), ), - kernel.NewClient(vppConn), + kernel.NewClient(vppConn, kernel.WithDump(dumpOption)), vxlan.NewClient(vppConn, tunnelIP, opts.vxlanOpts...), wireguard.NewClient(vppConn, tunnelIP), vlan.NewClient(vppConn, opts.domain2Device), diff --git a/pkg/networkservice/mechanisms/kernel/client.go b/pkg/networkservice/mechanisms/kernel/client.go index 69c18d8a..0d382211 100644 --- a/pkg/networkservice/mechanisms/kernel/client.go +++ b/pkg/networkservice/mechanisms/kernel/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Cisco and/or its affiliates. +// Copyright (c) 2020-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -30,9 +30,14 @@ import ( ) // NewClient - returns a new Client chain element implementing the kernel mechanism with vpp -func NewClient(vppConn api.Connection) networkservice.NetworkServiceClient { +func NewClient(vppConn api.Connection, opts ...Option) networkservice.NetworkServiceClient { + o := &options{} + for _, opt := range opts { + opt(o) + } + if _, err := os.Stat(vnetFilename); err == nil { - return kerneltap.NewClient(vppConn) + return kerneltap.NewClient(vppConn, kerneltap.WithDump(o.dumpOpt)) } return kernelvethpair.NewClient(vppConn) } diff --git a/pkg/networkservice/mechanisms/kernel/kerneltap/client.go b/pkg/networkservice/mechanisms/kernel/kerneltap/client.go index 3002f3b6..8c604198 100644 --- a/pkg/networkservice/mechanisms/kernel/kerneltap/client.go +++ b/pkg/networkservice/mechanisms/kernel/kerneltap/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Cisco and/or its affiliates. +// Copyright (c) 2020-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -32,16 +32,35 @@ import ( "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" "github.com/networkservicemesh/sdk/pkg/tools/log" "github.com/networkservicemesh/sdk/pkg/tools/postpone" + + "github.com/networkservicemesh/sdk-vpp/pkg/tools/dumptool" ) type kernelTapClient struct { vppConn api.Connection + dumpMap *dumptool.Map } // NewClient - return a new Client chain element implementing the kernel mechanism with vpp using tapv2 -func NewClient(vppConn api.Connection) networkservice.NetworkServiceClient { +func NewClient(vppConn api.Connection, opts ...Option) networkservice.NetworkServiceClient { + o := &options{} + for _, opt := range opts { + opt(o) + } + + ctx := context.Background() + dumpMap := dumptool.NewMap(ctx, 0) + if o.dumpOpt != nil { + var err error + dumpMap, err = dump(ctx, vppConn, o.dumpOpt.PodName, o.dumpOpt.Timeout, true) + if err != nil { + log.FromContext(ctx).Errorf("failed to Dump: %v", err) + } + } + return &kernelTapClient{ vppConn: vppConn, + dumpMap: dumpMap, } } @@ -60,7 +79,7 @@ func (k *kernelTapClient) Request(ctx context.Context, request *networkservice.N return nil, err } - if err := create(ctx, conn, k.vppConn, metadata.IsClient(k)); err != nil { + if err := create(ctx, conn, k.vppConn, k.dumpMap, metadata.IsClient(k)); err != nil { closeCtx, cancelClose := postponeCtxFunc() defer cancelClose() @@ -75,7 +94,7 @@ func (k *kernelTapClient) Request(ctx context.Context, request *networkservice.N } func (k *kernelTapClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { - err := del(ctx, conn, k.vppConn, metadata.IsClient(k)) + err := del(ctx, conn, k.vppConn, k.dumpMap, metadata.IsClient(k)) if err != nil { log.FromContext(ctx).Error(err) } diff --git a/pkg/networkservice/mechanisms/kernel/kerneltap/common.go b/pkg/networkservice/mechanisms/kernel/kerneltap/common.go index 2eb9af18..17eb7cce 100644 --- a/pkg/networkservice/mechanisms/kernel/kerneltap/common.go +++ b/pkg/networkservice/mechanisms/kernel/kerneltap/common.go @@ -34,12 +34,16 @@ import ( kernellink "github.com/networkservicemesh/sdk-kernel/pkg/kernel" "github.com/networkservicemesh/sdk/pkg/tools/log" + "github.com/networkservicemesh/sdk-vpp/pkg/tools/dumptool" "github.com/networkservicemesh/sdk-vpp/pkg/tools/ifindex" "github.com/networkservicemesh/sdk-vpp/pkg/tools/mechutils" ) -func create(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection, isClient bool) error { +func create(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection, dumpMap *dumptool.Map, isClient bool) error { if mechanism := kernel.ToMechanism(conn.GetMechanism()); mechanism != nil { + if val, loaded := dumpMap.LoadAndDelete(conn.GetId()); loaded { + ifindex.Store(ctx, isClient, val.(interface_types.InterfaceIndex)) + } // Construct the netlink handle for the target namespace for this kernel interface handle, err := kernellink.GetNetlinkHandle(mechanism.GetNetNSURL()) if err != nil { @@ -53,7 +57,7 @@ func create(ctx context.Context, conn *networkservice.Connection, vppConn api.Co } } // Delete the kernel interface if there is one in the target namespace - _ = del(ctx, conn, vppConn, isClient) + _ = del(ctx, conn, vppConn, dumpMap, isClient) nsFilename, err := mechutils.ToNSFilename(mechanism) if err != nil { @@ -141,8 +145,11 @@ func create(ctx context.Context, conn *networkservice.Connection, vppConn api.Co return nil } -func del(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection, isClient bool) error { +func del(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection, dumpMap *dumptool.Map, isClient bool) error { if mechanism := kernel.ToMechanism(conn.GetMechanism()); mechanism != nil { + if val, loaded := dumpMap.LoadAndDelete(conn.GetId()); loaded { + ifindex.Store(ctx, isClient, val.(interface_types.InterfaceIndex)) + } swIfIndex, ok := ifindex.LoadAndDelete(ctx, isClient) if !ok { return nil @@ -162,3 +169,21 @@ func del(ctx context.Context, conn *networkservice.Connection, vppConn api.Conne } return nil } + +func dump(ctx context.Context, vppConn api.Connection, podName string, timeout time.Duration, isClient bool) (*dumptool.Map, error) { + return dumptool.DumpVppInterfaces(ctx, vppConn, podName, timeout, isClient, + /* Function on dump */ + func(details *interfaces.SwInterfaceDetails) (interface{}, error) { + if details.InterfaceDevType == dumptool.DevTypeTap { + return details.SwIfIndex, nil + } + return nil, errors.New("Doesn't match the tap interface") + }, + /* Function on delete */ + func(ifindex interface{}) error { + _, err := tapv2.NewServiceClient(vppConn).TapDeleteV2(ctx, &tapv2.TapDeleteV2{ + SwIfIndex: ifindex.(interface_types.InterfaceIndex), + }) + return err + }) +} diff --git a/pkg/networkservice/mechanisms/kernel/kerneltap/options.go b/pkg/networkservice/mechanisms/kernel/kerneltap/options.go new file mode 100644 index 00000000..92c4a855 --- /dev/null +++ b/pkg/networkservice/mechanisms/kernel/kerneltap/options.go @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package kerneltap + +import "github.com/networkservicemesh/sdk-vpp/pkg/tools/dumptool" + +type options struct { + dumpOpt *dumptool.DumpOption +} + +// Option is an option pattern for kernel +type Option func(o *options) + +// WithDump - sets dump parameters +func WithDump(dump *dumptool.DumpOption) Option { + return func(o *options) { + o.dumpOpt = dump + } +} diff --git a/pkg/networkservice/mechanisms/kernel/kerneltap/server.go b/pkg/networkservice/mechanisms/kernel/kerneltap/server.go index 9436bbf8..5541734a 100644 --- a/pkg/networkservice/mechanisms/kernel/kerneltap/server.go +++ b/pkg/networkservice/mechanisms/kernel/kerneltap/server.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Cisco and/or its affiliates. +// Copyright (c) 2020-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -25,6 +25,8 @@ import ( "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" + "github.com/networkservicemesh/sdk-vpp/pkg/tools/dumptool" + "github.com/networkservicemesh/api/pkg/api/networkservice" "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" @@ -34,12 +36,29 @@ import ( type kernelTapServer struct { vppConn api.Connection + dumpMap *dumptool.Map } // NewServer - return a new Server chain element implementing the kernel mechanism with vpp using tapv2 -func NewServer(vppConn api.Connection) networkservice.NetworkServiceServer { +func NewServer(vppConn api.Connection, opts ...Option) networkservice.NetworkServiceServer { + o := &options{} + for _, opt := range opts { + opt(o) + } + + ctx := context.Background() + dumpMap := dumptool.NewMap(ctx, 0) + if o.dumpOpt != nil { + var err error + dumpMap, err = dump(ctx, vppConn, o.dumpOpt.PodName, o.dumpOpt.Timeout, false) + if err != nil { + log.FromContext(ctx).Errorf("failed to Dump: %v", err) + } + } + return &kernelTapServer{ vppConn: vppConn, + dumpMap: dumpMap, } } @@ -51,7 +70,7 @@ func (k *kernelTapServer) Request(ctx context.Context, request *networkservice.N return nil, err } - if err := create(ctx, conn, k.vppConn, metadata.IsClient(k)); err != nil { + if err := create(ctx, conn, k.vppConn, k.dumpMap, metadata.IsClient(k)); err != nil { closeCtx, cancelClose := postponeCtxFunc() defer cancelClose() @@ -66,7 +85,7 @@ func (k *kernelTapServer) Request(ctx context.Context, request *networkservice.N } func (k *kernelTapServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { - err := del(ctx, conn, k.vppConn, metadata.IsClient(k)) + err := del(ctx, conn, k.vppConn, k.dumpMap, metadata.IsClient(k)) if err != nil { log.FromContext(ctx).Error(err) } diff --git a/pkg/networkservice/mechanisms/kernel/options.go b/pkg/networkservice/mechanisms/kernel/options.go new file mode 100644 index 00000000..39231dfa --- /dev/null +++ b/pkg/networkservice/mechanisms/kernel/options.go @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux + +package kernel + +import "github.com/networkservicemesh/sdk-vpp/pkg/tools/dumptool" + +type options struct { + dumpOpt *dumptool.DumpOption +} + +// Option is an option pattern for kernel +type Option func(o *options) + +// WithDump - sets dump parameters +func WithDump(dump *dumptool.DumpOption) Option { + return func(o *options) { + o.dumpOpt = dump + } +} diff --git a/pkg/networkservice/mechanisms/kernel/server.go b/pkg/networkservice/mechanisms/kernel/server.go index 1f967f5a..58b02033 100644 --- a/pkg/networkservice/mechanisms/kernel/server.go +++ b/pkg/networkservice/mechanisms/kernel/server.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Cisco and/or its affiliates. +// Copyright (c) 2020-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -30,9 +30,14 @@ import ( ) // NewServer return a NetworkServiceServer chain element that correctly handles the kernel Mechanism -func NewServer(vppConn api.Connection) networkservice.NetworkServiceServer { +func NewServer(vppConn api.Connection, opts ...Option) networkservice.NetworkServiceServer { + o := &options{} + for _, opt := range opts { + opt(o) + } + if _, err := os.Stat(vnetFilename); err == nil { - return kerneltap.NewServer(vppConn) + return kerneltap.NewServer(vppConn, kerneltap.WithDump(o.dumpOpt)) } return kernelvethpair.NewServer(vppConn) } diff --git a/pkg/networkservice/tag/common.go b/pkg/networkservice/tag/common.go index 6b22d198..7bc2683e 100644 --- a/pkg/networkservice/tag/common.go +++ b/pkg/networkservice/tag/common.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" "github.com/networkservicemesh/sdk-vpp/pkg/tools/ifindex" + "github.com/networkservicemesh/sdk-vpp/pkg/tools/tagtool" ) func create(ctx context.Context, conn *networkservice.Connection, vppConn api.Connection, isClient bool) error { @@ -36,16 +37,21 @@ func create(ctx context.Context, conn *networkservice.Connection, vppConn api.Co } now := time.Now() + tag := tagtool.ConvertToString(&tagtool.Tag{ + PodName: conn.Path.PathSegments[conn.Path.Index].Name, + ConnID: conn.GetId(), + IsClient: isClient, + }) if _, err := interfaces.NewServiceClient(vppConn).SwInterfaceTagAddDel(ctx, &interfaces.SwInterfaceTagAddDel{ IsAdd: true, SwIfIndex: swIfIndex, - Tag: conn.GetId(), + Tag: tag, }); err != nil { return errors.WithStack(err) } log.FromContext(ctx). WithField("swIfIndex", swIfIndex). - WithField("tag", conn.GetId()). + WithField("tag", tag). WithField("duration", time.Since(now)). WithField("vppapi", "SwInterfaceTagAddDel").Debug("completed") return nil diff --git a/pkg/tools/dumptool/dumptool.go b/pkg/tools/dumptool/dumptool.go new file mode 100644 index 00000000..4582ecf7 --- /dev/null +++ b/pkg/tools/dumptool/dumptool.go @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dumptool + +import ( + "context" + "io" + "time" + + "git.fd.io/govpp.git/api" + interfaces "github.com/edwarnicke/govpp/binapi/interface" + "github.com/pkg/errors" + + "github.com/networkservicemesh/sdk-vpp/pkg/tools/tagtool" +) + +const ( + // DevTypeTap - tap interface dev type + DevTypeTap = "virtio" +) + +// DumpFn - filtering function of dumped NSM interfaces +type DumpFn func(details *interfaces.SwInterfaceDetails) (interface{}, error) + +// DeleteFn - interface removal function +type DeleteFn func(value interface{}) error + +// DumpOption - option that configures chain elements +type DumpOption struct { + PodName string + Timeout time.Duration +} + +// DumpVppInterfaces - dumps vpp interfaces by tag. It contains two functions: +// - onDump - determines what to do if we found an NSM interface during the dump +// - onDelete - calls the delete function on timeout +func DumpVppInterfaces(ctx context.Context, vppConn api.Connection, podName string, timeout time.Duration, isClient bool, onDump DumpFn, onDelete DeleteFn) (*Map, error) { + dumpMap := NewMap(ctx, timeout) + + client, err := interfaces.NewServiceClient(vppConn).SwInterfaceDump(ctx, &interfaces.SwInterfaceDump{}) + if err != nil { + return dumpMap, errors.Wrap(err, "SwInterfaceDump error") + } + defer func() { _ = client.Close() }() + + for { + details, err := client.Recv() + if err == io.EOF || details == nil { + break + } + + t, err := tagtool.ConvertFromString(details.Tag) + if err != nil { + continue + } + if t.PodName != podName || t.IsClient != isClient { + continue + } + + if val, err := onDump(details); err == nil && val != nil { + dumpMap.Store(t.ConnID, val, onDelete) + } + } + return dumpMap, nil +} diff --git a/pkg/tools/dumptool/gen.go b/pkg/tools/dumptool/gen.go new file mode 100644 index 00000000..1b2a2f23 --- /dev/null +++ b/pkg/tools/dumptool/gen.go @@ -0,0 +1,32 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dumptool + +import ( + "context" + "sync" +) + +//go:generate go-syncmap -output table_map.gen.go -type innerMap + +type innerVal struct { + val interface{} + cancelCtx context.CancelFunc +} + +// Map - sync.Map with key == string (connID) and value == interface{} +type innerMap sync.Map diff --git a/pkg/tools/dumptool/map.go b/pkg/tools/dumptool/map.go new file mode 100644 index 00000000..74360e29 --- /dev/null +++ b/pkg/tools/dumptool/map.go @@ -0,0 +1,116 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dumptool + +import ( + "context" + "time" + + "github.com/edwarnicke/govpp/binapi/interface_types" + "github.com/networkservicemesh/sdk/pkg/tools/clock" +) + +// Map - stores dumped vpp values and delete them if they are unused +type Map struct { + innerMap + ctx context.Context + timeout time.Duration +} + +// NewMap - creates new map for dumped interfaces +func NewMap(ctx context.Context, timeout time.Duration) *Map { + return &Map{ + ctx: ctx, + timeout: timeout, + } +} + +// Store - sets the value for a key. Starts a new goroutine with a timeout and delete the previous one +func (m *Map) Store(connID string, val interface{}, onDelete DeleteFn) { + // Cancel previous goroutine + if valWithCancel, loaded := m.innerMap.Load(connID); loaded { + valWithCancel.cancelCtx() + } + + valWithCancel := &innerVal{ + val: val, + } + + if onDelete != nil { + cancelCtx, cancel := context.WithCancel(m.ctx) + valWithCancel.cancelCtx = cancel + + timeClock := clock.FromContext(cancelCtx) + expireCh := timeClock.After(m.timeout) + go func() { + select { + case <-cancelCtx.Done(): + return + case <-expireCh: + if prevValWithCancel, loaded := m.innerMap.LoadAndDelete(connID); loaded { + _ = onDelete(prevValWithCancel.val.(interface_types.InterfaceIndex)) + } + } + }() + } + m.innerMap.Store(connID, valWithCancel) +} + +// LoadOrStore - returns the existing value for the key if present. Starts a new goroutine with a timeout if not loaded +func (m *Map) LoadOrStore(connID string, val interface{}, onDelete DeleteFn) (interface{}, bool) { + valWithCancel := &innerVal{ + val: val, + } + if onDelete != nil { + cancelCtx, cancel := context.WithCancel(m.ctx) + valWithCancel.cancelCtx = cancel + + timeClock := clock.FromContext(m.ctx) + expireCh := timeClock.After(m.timeout) + go func() { + select { + case <-cancelCtx.Done(): + return + case <-expireCh: + if prevValWithCancel, loaded := m.innerMap.LoadAndDelete(connID); loaded { + _ = onDelete(prevValWithCancel.val.(interface_types.InterfaceIndex)) + } + } + }() + } + + // If innerMap already has the value, we need to cancel the newly created goroutine + prevValWithCancel, loaded := m.innerMap.LoadOrStore(connID, valWithCancel) + if loaded { + valWithCancel.cancelCtx() + } + return prevValWithCancel.val, loaded +} + +// LoadAndDelete deletes the value for a key, returning the previous value if any. +func (m *Map) LoadAndDelete(key string) (interface{}, bool) { + valWithCancel, loaded := m.innerMap.LoadAndDelete(key) + if loaded { + valWithCancel.cancelCtx() + } + return valWithCancel.val, loaded +} + +// Delete deletes the value for a key. +func (m *Map) Delete(key string) { + _, _ = m.LoadAndDelete(key) +} diff --git a/pkg/tools/dumptool/table_map.gen.go b/pkg/tools/dumptool/table_map.gen.go new file mode 100644 index 00000000..6e80ffad --- /dev/null +++ b/pkg/tools/dumptool/table_map.gen.go @@ -0,0 +1,73 @@ +// Code generated by "-output table_map.gen.go -type innerMap -output table_map.gen.go -type innerMap"; DO NOT EDIT. +package dumptool + +import ( + "sync" // Used by sync.Map. +) + +// Generate code that will fail if the constants change value. +func _() { + // An "cannot convert innerMap literal (type innerMap) to type sync.Map" compiler error signifies that the base type have changed. + // Re-run the go-syncmap command to generate them again. + _ = (sync.Map)(innerMap{}) +} + +var _nil_innerMap_innerVal_value = func() (val *innerVal) { return }() + +// Load returns the value stored in the map for a key, or nil if no +// value is present. +// The ok result indicates whether value was found in the map. +func (m *innerMap) Load(key string) (*innerVal, bool) { + value, ok := (*sync.Map)(m).Load(key) + if value == nil { + return _nil_innerMap_innerVal_value, ok + } + return value.(*innerVal), ok +} + +// Store sets the value for a key. +func (m *innerMap) Store(key string, value *innerVal) { + (*sync.Map)(m).Store(key, value) +} + +// LoadOrStore returns the existing value for the key if present. +// Otherwise, it stores and returns the given value. +// The loaded result is true if the value was loaded, false if stored. +func (m *innerMap) LoadOrStore(key string, value *innerVal) (*innerVal, bool) { + actual, loaded := (*sync.Map)(m).LoadOrStore(key, value) + if actual == nil { + return _nil_innerMap_innerVal_value, loaded + } + return actual.(*innerVal), loaded +} + +// LoadAndDelete deletes the value for a key, returning the previous value if any. +// The loaded result reports whether the key was present. +func (m *innerMap) LoadAndDelete(key string) (value *innerVal, loaded bool) { + actual, loaded := (*sync.Map)(m).LoadAndDelete(key) + if actual == nil { + return _nil_innerMap_innerVal_value, loaded + } + return actual.(*innerVal), loaded +} + +// Delete deletes the value for a key. +func (m *innerMap) Delete(key string) { + (*sync.Map)(m).Delete(key) +} + +// Range calls f sequentially for each key and value present in the map. +// If f returns false, range stops the iteration. +// +// Range does not necessarily correspond to any consistent snapshot of the Map's +// contents: no key will be visited more than once, but if the value for any key +// is stored or deleted concurrently, Range may reflect any mapping for that key +// from any point during the Range call. +// +// Range may be O(N) with the number of elements in the map even if f returns +// false after a constant number of calls. +func (m *innerMap) Range(f func(key string, value *innerVal) bool) { + (*sync.Map)(m).Range(func(key, value interface{}) bool { + return f(key.(string), value.(*innerVal)) + }) +} diff --git a/pkg/tools/tagtool/tagtool.go b/pkg/tools/tagtool/tagtool.go new file mode 100644 index 00000000..23e08b3c --- /dev/null +++ b/pkg/tools/tagtool/tagtool.go @@ -0,0 +1,71 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tagtool provides some utilities for converting NSM interface tag +package tagtool + +import ( + "strings" + + "github.com/pkg/errors" +) + +const ( + clientIdentifier = "c" + serverIdentifier = "s" + delimiter = "_" +) + +// Tag - represent an NSM tag +type Tag struct { + PodName string + ConnID string + IsClient bool +} + +// ConvertToString - converts Tag to the string format: { forwarder-name }_{ identifier }_{ connID } +func ConvertToString(t *Tag) string { + isClientStr := clientIdentifier + if !t.IsClient { + isClientStr = serverIdentifier + } + return t.PodName + delimiter + isClientStr + delimiter + t.ConnID +} + +// ConvertFromString - converts from string to Tag +func ConvertFromString(tag string) (t *Tag, err error) { + if tag == "" { + return nil, errors.New("tag is empty") + } + substrs := strings.Split(tag, delimiter) + if len(substrs) != 3 { + return nil, errors.New("tag part count mismatch") + } + + t = &Tag{ + PodName: substrs[0], + ConnID: substrs[2], + } + switch substrs[1] { + case clientIdentifier: + t.IsClient = true + case serverIdentifier: + t.IsClient = false + default: + return nil, errors.New("identifier mismatch") + } + return t, nil +}