Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add vl3 chain elements and IPAM service #1247

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions pkg/ipam/vl3ipam/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// 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 vl3ipam provides implementation of api/pkg/api/ipam.IPAMServer for vL3 scenario.
package vl3ipam

import (
"net"
"sync"

"github.com/networkservicemesh/api/pkg/api/ipam"
"github.com/pkg/errors"

"github.com/networkservicemesh/sdk/pkg/tools/ippool"
)

// ErrUndefined means that operation is not supported
var ErrUndefined = errors.New("request type is undefined")

// ErrOutOfRange means that ip pool of IPAM is empty
var ErrOutOfRange = errors.New("prefix is out of range or already in use")

type vl3IPAMServer struct {
pool *ippool.IPPool
excludedPrefixes []string
poolMutex sync.Mutex
initalSize uint8
}

// NewIPAMServer creates a new ipam.IPAMServer handler for grpc.Server
func NewIPAMServer(prefix string, initialNSEPrefixSize uint8) ipam.IPAMServer {
return &vl3IPAMServer{
pool: ippool.NewWithNetString(prefix),
initalSize: initialNSEPrefixSize,
}
}

var _ ipam.IPAMServer = (*vl3IPAMServer)(nil)

func (s *vl3IPAMServer) ManagePrefixes(prefixServer ipam.IPAM_ManagePrefixesServer) error {
var pool = s.pool
var mutex = &s.poolMutex
var clientsPrefixes []string
var err error

for err == nil {
var r *ipam.PrefixRequest

r, err = prefixServer.Recv()
if err != nil {
break
}

switch r.Type {
case ipam.Type_UNDEFINED:
return ErrUndefined

case ipam.Type_ALLOCATE:
var resp ipam.PrefixResponse
mutex.Lock()
for _, excludePrefix := range r.ExcludePrefixes {
pool.ExcludeString(excludePrefix)
}
resp.Prefix = r.Prefix
if resp.Prefix == "" || !pool.ContainsNetString(resp.Prefix) {
var ip net.IP
ip, err = pool.Pull()
if err != nil {
mutex.Unlock()
break
}
ipNet := &net.IPNet{
IP: ip,
Mask: net.CIDRMask(
int(s.initalSize),
len(ip)*8,
),
}
resp.Prefix = ipNet.String()
}
s.excludedPrefixes = append(s.excludedPrefixes, r.Prefix)
clientsPrefixes = append(clientsPrefixes, resp.Prefix)
pool.ExcludeString(resp.Prefix)
mutex.Unlock()
resp.ExcludePrefixes = r.ExcludePrefixes
resp.ExcludePrefixes = append(resp.ExcludePrefixes, s.excludedPrefixes...)
err = prefixServer.Send(&resp)

case ipam.Type_DELETE:
for i, p := range clientsPrefixes {
if p != r.Prefix {
continue
}
mutex.Lock()
pool.AddNetString(p)
mutex.Unlock()
clientsPrefixes = append(clientsPrefixes[:i], clientsPrefixes[i+1:]...)
break
}
}
}

s.poolMutex.Lock()
for _, prefix := range clientsPrefixes {
pool.AddNetString(prefix)
}
s.poolMutex.Unlock()

if prefixServer.Context().Err() != nil {
return nil
}

return err
}
153 changes: 153 additions & 0 deletions pkg/ipam/vl3ipam/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// 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 vl3ipam_test

import (
"context"
"fmt"
"net/url"
"testing"
"time"

"github.com/networkservicemesh/api/pkg/api/ipam"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"google.golang.org/grpc"

"github.com/networkservicemesh/sdk/pkg/ipam/vl3ipam"
"github.com/networkservicemesh/sdk/pkg/tools/grpcutils"
)

func newVL3IPAMServer(ctx context.Context, t *testing.T, prefix string, initialSize uint8) url.URL {
var s = grpc.NewServer()
ipam.RegisterIPAMServer(s, vl3ipam.NewIPAMServer(prefix, initialSize))

var serverAddr url.URL

require.Len(t, grpcutils.ListenAndServe(ctx, &serverAddr, s), 0)

return serverAddr
}

func newVL3IPAMClient(ctx context.Context, t *testing.T, connectTO *url.URL) ipam.IPAMClient {
var cc, err = grpc.DialContext(ctx, grpcutils.URLToTarget(connectTO), grpc.WithInsecure())
require.NoError(t, err)

go func() {
<-ctx.Done()
_ = cc.Close()
}()

return ipam.NewIPAMClient(cc)
}

func Test_vl3_IPAM_Allocate(t *testing.T) {
t.Cleanup(func() {
goleak.VerifyNone(t)
})

var ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

connectTO := newVL3IPAMServer(ctx, t, "172.16.0.0/16", 24)

for i := 0; i < 10; i++ {
c := newVL3IPAMClient(ctx, t, &connectTO)

var stream, err = c.ManagePrefixes(ctx)

require.NoError(t, err, i)

err = stream.Send(&ipam.PrefixRequest{
Type: ipam.Type_ALLOCATE,
})

require.NoError(t, err)

resp, err := stream.Recv()
require.NoError(t, err)

require.Equal(t, fmt.Sprintf("172.16.%v.0/24", i), resp.Prefix, i)
require.NotEmpty(t, resp.ExcludePrefixes)
}
}

func Test_vl3_IPAM_Allocate2(t *testing.T) {
t.Cleanup(func() {
goleak.VerifyNone(t)
})

var ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
connectTO := newVL3IPAMServer(ctx, t, "173.16.0.0/16", 24)

for i := 0; i < 10; i++ {
clientCTX, cancel := context.WithCancel(ctx)
c := newVL3IPAMClient(clientCTX, t, &connectTO)

var stream, err = c.ManagePrefixes(clientCTX)
require.NoError(t, err, i)

err = stream.Send(&ipam.PrefixRequest{
Type: ipam.Type_ALLOCATE,
})

require.NoError(t, err)

resp, err := stream.Recv()
require.NoError(t, err)

require.Equal(t, "173.16.0.0/24", resp.Prefix, i)
require.NotEmpty(t, resp.ExcludePrefixes, i)
cancel()
time.Sleep(time.Millisecond * 50)
}
}

func Test_vl3_IPAM_Allocate3(t *testing.T) {
t.Cleanup(func() {
goleak.VerifyNone(t)
})

var ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

connectTO := newVL3IPAMServer(ctx, t, "172.16.0.0/16", 24)

for i := 0; i < 10; i++ {
clientCTX, cancel := context.WithCancel(ctx)
c := newVL3IPAMClient(clientCTX, t, &connectTO)

var stream, err = c.ManagePrefixes(clientCTX)
require.NoError(t, err, i)

err = stream.Send(&ipam.PrefixRequest{
Type: ipam.Type_ALLOCATE,
Prefix: "172.16.0.0/30",
})

require.NoError(t, err)

resp, err := stream.Recv()
require.NoError(t, err)

require.Equal(t, "172.16.0.0/30", resp.Prefix, i)
require.NotEmpty(t, resp.ExcludePrefixes, i)
cancel()
time.Sleep(time.Millisecond * 50)
}
}
Loading