Skip to content

Commit

Permalink
feat: add groupipam networkservice server chain element (#1407)
Browse files Browse the repository at this point in the history
* add groupipam networkservice server chain element

Signed-off-by: denis-tingaikin <denis.tingajkin@xored.com>

* fix linter

Signed-off-by: denis-tingaikin <denis.tingajkin@xored.com>

Signed-off-by: denis-tingaikin <denis.tingajkin@xored.com>
  • Loading branch information
denis-tingaikin authored Jan 9, 2023
1 parent 75d6d5d commit 61629de
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 0 deletions.
67 changes: 67 additions & 0 deletions pkg/networkservice/ipam/groupipam/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2023 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 groupipam provides a networkservice.NetworkServiceServer chain element to handle a group of []*net.IPNet.
// The chain element should be used when the endpoint should assign a few addresses for the connection.
// By default `groupipam` uses `point2pointipam` to handle *net.IPNet.
package groupipam

import (
"net"

"github.com/networkservicemesh/api/pkg/api/networkservice"

"github.com/networkservicemesh/sdk/pkg/networkservice/core/chain"
"github.com/networkservicemesh/sdk/pkg/networkservice/ipam/point2pointipam"
)

type options struct {
newIPAMServerFn func(...*net.IPNet) networkservice.NetworkServiceServer
}

// Option allows to change a default behavior
type Option func(*options)

// WithCustomIPAMServer replaces default `point2pointipam` to custom implementation
func WithCustomIPAMServer(f func(...*net.IPNet) networkservice.NetworkServiceServer) Option {
if f == nil {
panic("nil is not allowed")
}

return func(o *options) {
o.newIPAMServerFn = f
}
}

// NewServer creates a new instance of groupipam chain element that handles a group of []*net.IPNet.
// Requires a group of []*net.IPNet.
// Options can be passed optionally
func NewServer(groups [][]*net.IPNet, opts ...Option) networkservice.NetworkServiceServer {
var ipamServers []networkservice.NetworkServiceServer
var o = options{
newIPAMServerFn: point2pointipam.NewServer,
}

for _, opt := range opts {
opt(&o)
}

for _, group := range groups {
ipamServers = append(ipamServers, o.newIPAMServerFn(group...))
}

return chain.NewNetworkServiceServer(ipamServers...)
}
128 changes: 128 additions & 0 deletions pkg/networkservice/ipam/groupipam/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) 2023 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 groupipam_test

import (
"context"
"net"
"testing"

"github.com/networkservicemesh/api/pkg/api/networkservice"
"github.com/stretchr/testify/require"

"github.com/networkservicemesh/sdk/pkg/networkservice/common/updatepath"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/chain"
"github.com/networkservicemesh/sdk/pkg/networkservice/ipam/groupipam"
"github.com/networkservicemesh/sdk/pkg/networkservice/ipam/singlepointipam"
"github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata"
)

func Test_NewServer_ShouldFailIfOptionIsNil(t *testing.T) {
require.Panics(t, func() {
groupipam.NewServer([][]*net.IPNet{}, groupipam.WithCustomIPAMServer(nil))
})
}

func Test_NewServer_UsesPoint2pointIPAMByDefault(t *testing.T) {
requireConns := func(t *testing.T, conn *networkservice.Connection, dsts, srcs []string) {
for i, dst := range dsts {
require.Equal(t, conn.Context.IpContext.DstIpAddrs[i], dst)
require.Equal(t, conn.Context.IpContext.SrcRoutes[i].Prefix, dst)
}
for i, src := range srcs {
require.Equal(t, conn.Context.IpContext.SrcIpAddrs[i], src)
require.Equal(t, conn.Context.IpContext.DstRoutes[i].Prefix, src)
}
}

_, ipNet1, err := net.ParseCIDR("172.92.3.4/16")
require.NoError(t, err)
_, ipNet2, err := net.ParseCIDR("fe80::/64")
require.NoError(t, err)

srv := groupipam.NewServer([][]*net.IPNet{{ipNet1}, {ipNet2}})

req := &networkservice.NetworkServiceRequest{
Connection: &networkservice.Connection{
Context: &networkservice.ConnectionContext{
IpContext: new(networkservice.IPContext),
},
},
}

req.Connection.Context.IpContext.ExcludedPrefixes = []string{"172.92.0.1/32", "fe80::1/128"}
conn, err := srv.Request(context.Background(), req.Clone())
require.NoError(t, err)
requireConns(t, conn, []string{"172.92.0.0/32", "fe80::/128"}, []string{"172.92.0.2/32", "fe80::2/128"})

req.Connection = conn
conn, err = srv.Request(context.Background(), req.Clone())
require.NoError(t, err)
requireConns(t, conn, []string{"172.92.0.0/32", "fe80::/128"}, []string{"172.92.0.2/32", "fe80::2/128"})

req.Connection = conn.Clone()
req.Connection.Context.IpContext.ExcludedPrefixes = []string{"172.92.0.1/30", "fe80::1/126"}
conn, err = srv.Request(context.Background(), req)
require.NoError(t, err)
requireConns(t, conn, []string{"172.92.0.4/32", "fe80::4/128"}, []string{"172.92.0.5/32", "fe80::5/128"})
}

func Test_NewServer_GroupOfCustomIPAMServers(t *testing.T) {
requireConns := func(t *testing.T, conn *networkservice.Connection, srcs []string) {
require.Equal(t, len(srcs), len(conn.Context.IpContext.SrcIpAddrs))
for i, src := range srcs {
require.Equal(t, src, conn.Context.IpContext.SrcIpAddrs[i])
}
}

_, ipNet1, err := net.ParseCIDR("172.92.3.4/16")
require.NoError(t, err)
_, ipNet2, err := net.ParseCIDR("fd00::/8")
require.NoError(t, err)

srv := chain.NewNetworkServiceServer(
updatepath.NewServer("ipam"),
metadata.NewServer(),
groupipam.NewServer([][]*net.IPNet{{ipNet1}, {ipNet2}}, groupipam.WithCustomIPAMServer(singlepointipam.NewServer)),
)
req := &networkservice.NetworkServiceRequest{
Connection: &networkservice.Connection{
Context: &networkservice.ConnectionContext{
IpContext: new(networkservice.IPContext),
},
},
}

conn1, err := srv.Request(context.Background(), req.Clone())
require.NoError(t, err)
requireConns(t, conn1, []string{"172.92.0.1/16", "fd00::1/8"})

conn2, err := srv.Request(context.Background(), req.Clone())
require.NoError(t, err)
requireConns(t, conn2, []string{"172.92.0.2/16", "fd00::2/8"})

_, err = srv.Close(context.Background(), conn1)
require.NoError(t, err)

conn3, err := srv.Request(context.Background(), req.Clone())
require.NoError(t, err)
requireConns(t, conn3, []string{"172.92.0.1/16", "fd00::1/8"})

conn4, err := srv.Request(context.Background(), req.Clone())
require.NoError(t, err)
requireConns(t, conn4, []string{"172.92.0.3/16", "fd00::3/8"})
}

0 comments on commit 61629de

Please sign in to comment.