diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2752349a3..fb2d7e06e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,15 +37,24 @@ jobs: golangci-lint: name: golangci-lint runs-on: ubuntu-latest + strategy: + matrix: + os: [linux, darwin, windows] steps: - name: Check out code into the Go module directory 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 + uses: golangci/golangci-lint-action@v3 with: version: v1.42.1 + env: + GOOS: ${{ matrix.goos }} excludeFmtErrorf: name: exclude fmt.Errorf runs-on: ubuntu-latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 810665e8a..5f95ac1e4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -53,6 +53,8 @@ jobs: - cmd-nse-remote-vlan - cmd-nse-vfio - cmd-nsc-init + - cmd-ipam-vl3 + - cmd-map-ip-k8s - cmd-admission-webhook-k8s name: Update ${{ matrix.repository }} needs: create-release diff --git a/.github/workflows/update-dependent-repositories-gomod.yaml b/.github/workflows/update-dependent-repositories-gomod.yaml index d399365ae..b54eecb44 100644 --- a/.github/workflows/update-dependent-repositories-gomod.yaml +++ b/.github/workflows/update-dependent-repositories-gomod.yaml @@ -25,6 +25,8 @@ jobs: - cmd-nse-vfio - cmd-nse-remote-vlan - cmd-nsc-init + - cmd-ipam-vl3 + - cmd-map-ip-k8s - cmd-admission-webhook-k8s name: Update ${{ matrix.repository }} runs-on: ubuntu-latest diff --git a/go.mod b/go.mod index ae45d8bde..bbceabc38 100644 --- a/go.mod +++ b/go.mod @@ -15,9 +15,9 @@ require ( github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.6 github.com/google/uuid v1.1.2 - github.com/nats-io/nats-streaming-server v0.24.1 + github.com/nats-io/nats-streaming-server v0.24.3 github.com/nats-io/stan.go v0.10.2 - github.com/networkservicemesh/api v1.1.2-0.20220119092736-21eda250c390 + github.com/networkservicemesh/api v1.3.0-rc.1.0.20220405210054-fbcde048efa5 github.com/open-policy-agent/opa v0.16.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 diff --git a/go.sum b/go.sum index b3d66cd3d..875266e3a 100644 --- a/go.sum +++ b/go.sum @@ -101,7 +101,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -137,8 +136,8 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/raft v1.3.3 h1:Xr6DSHC5cIM8kzxu+IgoT/+MeNeUNeWin3ie6nlSrMg= -github.com/hashicorp/raft v1.3.3/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= +github.com/hashicorp/raft v1.3.6 h1:v5xW5KzByoerQlN/o31VJrFNiozgzGyDoMgDJgXpsto= +github.com/hashicorp/raft v1.3.6/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -151,9 +150,9 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4= +github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -172,28 +171,28 @@ github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW1 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= -github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mna/pigeon v0.0.0-20180808201053-bb0192cfc2ae/go.mod h1:Iym28+kJVnC1hfQvv5MUtI6AiFFzvQjHcvI4RFTG/04= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 h1:vU9tpM3apjYlLLeY23zRWJ9Zktr5jp+mloR942LEOpY= github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= -github.com/nats-io/nats-server/v2 v2.7.2 h1:+LEN8m0+jdCkiGc884WnDuxR+qj80/5arj+szKuRpRI= -github.com/nats-io/nats-server/v2 v2.7.2/go.mod h1:tckmrt0M6bVaDT3kmh9UrIq/CBOBBse+TpXQi5ldaa8= -github.com/nats-io/nats-streaming-server v0.24.1 h1:autzhooN72ELtqP3alC2OPzmrbiA6jIZaQmKdLQsckk= -github.com/nats-io/nats-streaming-server v0.24.1/go.mod h1:N2Q05hKD+aW2Ur1VYP85yUR2zUWHbqJG88CxAFLRrd4= +github.com/nats-io/nats-server/v2 v2.7.4 h1:c+BZJ3rGzUKCBIM4IXO8uNT2u1vajGbD1kPA6wqCEaM= +github.com/nats-io/nats-server/v2 v2.7.4/go.mod h1:1vZ2Nijh8tcyNe8BDVyTviCd9NYzRbubQYiEHsvOQWc= +github.com/nats-io/nats-streaming-server v0.24.3 h1:uZez8jBkXscua++jaDsK7DhpSAkizdetar6yWbPMRco= +github.com/nats-io/nats-streaming-server v0.24.3/go.mod h1:rqWfyCbxlhKj//fAp8POdQzeADwqkVhZcoWlbhkuU5w= github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= -github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d h1:GRSmEJutHkdoxKsRypP575IIdoXe7Bm6yHQF6GcDBnA= -github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d h1:zJf4l8Kp67RIZhoVeniSLZs69SHNgjLHz0aNsqPPlx8= +github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/stan.go v0.10.2 h1:gQLd05LhzmhFkHm3/qP/klYHfM/hys45GyHa1Uly/kI= github.com/nats-io/stan.go v0.10.2/go.mod h1:vo2ax8K2IxaR3JtEMLZRFKIdoK/3o1/PKueapB7ezX0= -github.com/networkservicemesh/api v1.1.2-0.20220119092736-21eda250c390 h1:GREf14twiQkUInGh+M0qQH+jNQ9uvTfQC2FVieDd7tQ= -github.com/networkservicemesh/api v1.1.2-0.20220119092736-21eda250c390/go.mod h1:B6meq/SWjWR6bGXZdXPfbOeaBK+T1JayLdtEJQCsXKU= +github.com/networkservicemesh/api v1.3.0-rc.1.0.20220405210054-fbcde048efa5 h1:5zQY4PhShozvj/GFJS0dX6ocamAB9oWwEOJviAhGUaw= +github.com/networkservicemesh/api v1.3.0-rc.1.0.20220405210054-fbcde048efa5/go.mod h1:B6meq/SWjWR6bGXZdXPfbOeaBK+T1JayLdtEJQCsXKU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -313,8 +312,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -384,8 +384,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= +golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/pkg/ipam/vl3ipam/server.go b/pkg/ipam/vl3ipam/server.go new file mode 100644 index 000000000..d8bcb946b --- /dev/null +++ b/pkg/ipam/vl3ipam/server.go @@ -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 +} diff --git a/pkg/ipam/vl3ipam/server_test.go b/pkg/ipam/vl3ipam/server_test.go new file mode 100644 index 000000000..035c2170b --- /dev/null +++ b/pkg/ipam/vl3ipam/server_test.go @@ -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) + } +} diff --git a/pkg/networkservice/chains/nsmgr/heal_test.go b/pkg/networkservice/chains/nsmgr/heal_test.go index 4c4953f21..0711084e7 100644 --- a/pkg/networkservice/chains/nsmgr/heal_test.go +++ b/pkg/networkservice/chains/nsmgr/heal_test.go @@ -18,6 +18,7 @@ package nsmgr_test import ( "context" + "fmt" "testing" "time" @@ -27,6 +28,8 @@ import ( "github.com/networkservicemesh/api/pkg/api/registry" + nsclient "github.com/networkservicemesh/sdk/pkg/networkservice/chains/client" + "github.com/networkservicemesh/sdk/pkg/networkservice/common/null" "github.com/networkservicemesh/sdk/pkg/networkservice/utils/count" "github.com/networkservicemesh/sdk/pkg/registry/chains/client" "github.com/networkservicemesh/sdk/pkg/tools/sandbox" @@ -445,3 +448,116 @@ func checkSecondRequestsReceived(requestsDone func() int) func() bool { return requestsDone() >= 2 } } + +func Test_ForwarderShouldBeSelectedCorrectlyOnNSMgrRestart(t *testing.T) { + var samples = []struct { + name string + nodeNum int + pathSegmentCount int + }{ + { + name: "Local", + nodeNum: 0, + pathSegmentCount: 4, + }, + { + name: "Remote", + nodeNum: 1, + pathSegmentCount: 6, + }, + } + + for _, sample := range samples { + t.Run(sample.name, func(t *testing.T) { + // nolint:scopelint + testForwarderShouldBeSelectedCorrectlyOnNSMgrRestart(t, sample.nodeNum, sample.pathSegmentCount) + }) + } +} + +func testForwarderShouldBeSelectedCorrectlyOnNSMgrRestart(t *testing.T, nodeNum, pathSegmentCount int) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + domain := sandbox.NewBuilder(ctx, t). + SetNodesCount(nodeNum + 1). + SetRegistryProxySupplier(nil). + SetNSMgrProxySupplier(nil). + Build() + + var expectedForwarderName string + + require.Len(t, domain.Nodes[nodeNum].Forwarders, 1) + for k := range domain.Nodes[nodeNum].Forwarders { + expectedForwarderName = k + } + + nsRegistryClient := domain.NewNSRegistryClient(ctx, sandbox.GenerateTestToken) + + _, err := nsRegistryClient.Register(ctx, ®istry.NetworkService{ + Name: "my-ns", + }) + require.NoError(t, err) + + nseReg := ®istry.NetworkServiceEndpoint{ + Name: "my-nse-1", + NetworkServiceNames: []string{"my-ns"}, + } + + nseEntry := domain.Nodes[nodeNum].NewEndpoint(ctx, nseReg, sandbox.GenerateTestToken) + + nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, nsclient.WithHealClient(null.NewClient())) + + request := defaultRequest("my-ns") + + conn, err := nsc.Request(ctx, request.Clone()) + require.NoError(t, err) + require.NotNil(t, conn) + require.Equal(t, pathSegmentCount, len(conn.Path.PathSegments)) + require.Equal(t, expectedForwarderName, conn.GetPath().GetPathSegments()[pathSegmentCount-2].Name) + + for i := 0; i < 10; i++ { + request.Connection = conn.Clone() + conn, err = nsc.Request(ctx, request.Clone()) + + require.NoError(t, err) + require.Equal(t, expectedForwarderName, conn.GetPath().GetPathSegments()[pathSegmentCount-2].Name) + + domain.Nodes[nodeNum].NSMgr.Restart() + + _, err = domain.Nodes[nodeNum].NSMgr.NetworkServiceEndpointRegistryServer().Register(ctx, ®istry.NetworkServiceEndpoint{ + Name: nseReg.Name, + Url: nseEntry.URL.String(), + NetworkServiceNames: nseReg.NetworkServiceNames, + }) + require.NoError(t, err) + + _, err = domain.Nodes[nodeNum].NSMgr.NetworkServiceEndpointRegistryServer().Register(ctx, ®istry.NetworkServiceEndpoint{ + Name: expectedForwarderName, + Url: domain.Nodes[nodeNum].Forwarders[expectedForwarderName].URL.String(), + NetworkServiceNames: []string{"forwarder"}, + NetworkServiceLabels: map[string]*registry.NetworkServiceLabels{ + "forwarder": { + Labels: map[string]string{ + "p2p": "true", + }, + }, + }, + }) + require.NoError(t, err) + + domain.Nodes[nodeNum].NewForwarder(ctx, ®istry.NetworkServiceEndpoint{ + Name: sandbox.UniqueName(fmt.Sprintf("%v-forwarder", i)), + NetworkServiceNames: []string{"forwarder"}, + NetworkServiceLabels: map[string]*registry.NetworkServiceLabels{ + "forwarder": { + Labels: map[string]string{ + "p2p": "true", + }, + }, + }, + }, sandbox.GenerateTestToken) + } +} diff --git a/pkg/networkservice/chains/nsmgr/single_test.go b/pkg/networkservice/chains/nsmgr/single_test.go index cd3392562..33a4a9900 100644 --- a/pkg/networkservice/chains/nsmgr/single_test.go +++ b/pkg/networkservice/chains/nsmgr/single_test.go @@ -20,18 +20,26 @@ import ( "context" "fmt" "io/ioutil" + "net" + "net/url" "os" "path/filepath" "testing" "time" + "github.com/google/uuid" "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/cls" + kernelmech "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/kernel" "github.com/networkservicemesh/api/pkg/api/registry" "github.com/stretchr/testify/require" "go.uber.org/goleak" + "github.com/networkservicemesh/sdk/pkg/networkservice/chains/client" "github.com/networkservicemesh/sdk/pkg/networkservice/chains/nsmgr" + "github.com/networkservicemesh/sdk/pkg/networkservice/common/excludedprefixes" "github.com/networkservicemesh/sdk/pkg/networkservice/connectioncontext/dnscontext" + "github.com/networkservicemesh/sdk/pkg/networkservice/ipam/point2pointipam" "github.com/networkservicemesh/sdk/pkg/tools/clientinfo" "github.com/networkservicemesh/sdk/pkg/tools/sandbox" ) @@ -74,11 +82,11 @@ func Test_DNSUsecase(t *testing.T) { const expectedCorefile = ". {\n\tlog\n\treload\n}\nmy.domain1 {\n\tfanout . 8.8.4.4 8.8.8.8\n\tcache\n\tlog\n}\n" - nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, dnscontext.NewClient( + nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, client.WithAdditionalFunctionality(dnscontext.NewClient( dnscontext.WithChainContext(ctx), dnscontext.WithCorefilePath(corefilePath), dnscontext.WithResolveConfigPath(resolveConfigPath), - )) + ))) conn, err := nsc.Request(ctx, defaultRequest(nsReg.Name)) require.NoError(t, err) @@ -99,6 +107,136 @@ func Test_DNSUsecase(t *testing.T) { require.NoError(t, err) } +func Test_AwareNSEs(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + domain := sandbox.NewBuilder(ctx, t). + SetNodesCount(1). + SetNSMgrProxySupplier(nil). + SetRegistryProxySupplier(nil). + Build() + + nsRegistryClient := domain.NewNSRegistryClient(ctx, sandbox.GenerateTestToken) + + _, ipNet, err := net.ParseCIDR("172.16.0.96/29") + require.NoError(t, err) + + const count = 3 + var nseRegs [count]*registry.NetworkServiceEndpoint + var nses [count]*sandbox.EndpointEntry + var requests [count]*networkservice.NetworkServiceRequest + + ns1 := defaultRegistryService("my-ns-1") + ns2 := defaultRegistryService("my-ns-2") + + nsurl1, err := url.Parse(fmt.Sprintf("kernel://%s?%s=%s", ns1.Name, "color", "red")) + require.NoError(t, err) + + nsurl2, err := url.Parse(fmt.Sprintf("kernel://%s?%s=%s", ns2.Name, "color", "red")) + require.NoError(t, err) + + nsInfo := [count]struct { + ns *registry.NetworkService + labelKey string + labelValue string + }{ + { + ns: ns1, + labelKey: "color", + labelValue: "red", + }, + { + ns: ns2, + labelKey: "color", + labelValue: "red", + }, + { + ns: ns1, + labelKey: "day", + labelValue: "friday", + }, + } + + for i := 0; i < count; i++ { + nseRegs[i] = ®istry.NetworkServiceEndpoint{ + Name: fmt.Sprintf("nse-%s", uuid.New().String()), + NetworkServiceNames: []string{nsInfo[i].ns.Name}, + NetworkServiceLabels: map[string]*registry.NetworkServiceLabels{ + nsInfo[i].ns.Name: { + Labels: map[string]string{ + nsInfo[i].labelKey: nsInfo[i].labelValue, + }, + }, + }, + } + + nses[i] = domain.Nodes[0].NewEndpoint(ctx, nseRegs[i], sandbox.GenerateTestToken, point2pointipam.NewServer(ipNet)) + + requests[i] = &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: fmt.Sprint(i), + NetworkService: nsInfo[i].ns.Name, + Context: &networkservice.ConnectionContext{}, + Mechanism: &networkservice.Mechanism{Cls: cls.LOCAL, Type: kernelmech.MECHANISM}, + Labels: map[string]string{ + nsInfo[i].labelKey: nsInfo[i].labelValue, + }, + }, + } + + nsInfo[i].ns.Matches = append(nsInfo[i].ns.Matches, + ®istry.Match{ + SourceSelector: map[string]string{nsInfo[i].labelKey: nsInfo[i].labelValue}, + Routes: []*registry.Destination{ + { + DestinationSelector: map[string]string{nsInfo[i].labelKey: nsInfo[i].labelValue}, + }, + }, + }, + ) + } + + _, err = nsRegistryClient.Register(ctx, ns1) + require.NoError(t, err) + _, err = nsRegistryClient.Register(ctx, ns2) + require.NoError(t, err) + + nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, client.WithAdditionalFunctionality( + excludedprefixes.NewClient(excludedprefixes.WithAwarenessGroups( + [][]*url.URL{ + {nsurl1, nsurl2}, + }, + )))) + + var conns [count]*networkservice.Connection + for i := 0; i < count; i++ { + conns[i], err = nsc.Request(ctx, requests[i]) + require.NoError(t, err) + require.Equal(t, conns[0].NetworkServiceEndpointName, nses[0].Name) + } + + srcIP1 := conns[0].GetContext().GetIpContext().GetSrcIpAddrs() + srcIP2 := conns[1].GetContext().GetIpContext().GetSrcIpAddrs() + srcIP3 := conns[2].GetContext().GetIpContext().GetSrcIpAddrs() + + require.Equal(t, srcIP1[0], srcIP2[0]) + require.NotEqual(t, srcIP1[0], srcIP3[0]) + require.NotEqual(t, srcIP2[0], srcIP3[0]) + + for i := 0; i < count; i++ { + _, err = nsc.Close(ctx, conns[i]) + require.NoError(t, err) + } + + for i := 0; i < count; i++ { + _, err = nses[i].Unregister(ctx, nseRegs[i]) + require.NoError(t, err) + } +} + func Test_ShouldParseNetworkServiceLabelsTemplate(t *testing.T) { t.Cleanup(func() { goleak.VerifyNone(t) }) diff --git a/pkg/networkservice/chains/nsmgr/unix_test.go b/pkg/networkservice/chains/nsmgr/unix_test.go index 95087f195..cefa9b68e 100644 --- a/pkg/networkservice/chains/nsmgr/unix_test.go +++ b/pkg/networkservice/chains/nsmgr/unix_test.go @@ -29,6 +29,7 @@ import ( "github.com/networkservicemesh/api/pkg/api/registry" + "github.com/networkservicemesh/sdk/pkg/networkservice/chains/client" "github.com/networkservicemesh/sdk/pkg/networkservice/chains/nsmgr" "github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms/kernel" "github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms/recvfd" @@ -132,7 +133,7 @@ func Test_MultiForwarderSendfd(t *testing.T) { domain.Nodes[0].NewEndpoint(ctx, nseReg, sandbox.GenerateTestToken, counter) - nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, kernel.NewClient(), sendfd.NewClient()) + nsc := domain.Nodes[0].NewClient(ctx, sandbox.GenerateTestToken, client.WithAdditionalFunctionality(kernel.NewClient(), sendfd.NewClient())) request := defaultRequest(nsReg.Name) diff --git a/pkg/networkservice/common/begin/merge.go b/pkg/networkservice/common/begin/merge.go index 3d92174b9..54395ef89 100644 --- a/pkg/networkservice/common/begin/merge.go +++ b/pkg/networkservice/common/begin/merge.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -21,39 +21,55 @@ import ( "google.golang.org/protobuf/proto" ) -func mergeConnection(returnedConnection, requestedConnection, connection *networkservice.Connection) *networkservice.Connection { - if returnedConnection == nil || connection == nil { +// mergeConnection - preforms the three way merge of the returnedConnection, requestedConnection and connection +// returnedConnection - the Connection last returned from the begin.Request(...) +// requestedConnection - the Connection passed in to the begin.Request(...) +// currentConnection - the last value for the Connection in EventFactory. Since Refreshes, Heals, etc +// can result in changes that have *not* been returned from begin.Request(...) because +// they originated in events internal to the chain (instead of external via calls to +// begin.Request(...)) it is possible that connection differs from returnedConnection +func mergeConnection(returnedConnection, requestedConnection, currentConnection *networkservice.Connection) *networkservice.Connection { + if returnedConnection == nil || currentConnection == nil { return requestedConnection } - conn := connection.Clone() + conn := currentConnection.Clone() if returnedConnection.GetNetworkServiceEndpointName() != requestedConnection.GetNetworkServiceEndpointName() { conn.NetworkServiceEndpointName = requestedConnection.GetNetworkServiceEndpointName() } - conn.Context = mergeConnectionContext(returnedConnection.GetContext(), requestedConnection.GetContext(), connection.GetContext()) + conn.Context = mergeConnectionContext(returnedConnection.GetContext(), requestedConnection.GetContext(), currentConnection.GetContext()) return conn } -func mergeConnectionContext(returnedConnectionContext, requestedConnectionContext, connectioncontext *networkservice.ConnectionContext) *networkservice.ConnectionContext { - rv := proto.Clone(connectioncontext).(*networkservice.ConnectionContext) +func mergeConnectionContext(returnedConnectionContext, requestedConnectionContext, currentConnectionContext *networkservice.ConnectionContext) *networkservice.ConnectionContext { + if currentConnectionContext == nil { + return requestedConnectionContext + } + rv := proto.Clone(currentConnectionContext).(*networkservice.ConnectionContext) if !proto.Equal(returnedConnectionContext, requestedConnectionContext) { - // TODO: IPContext, DNSContext, EthernetContext, do we need to do MTU? - rv.ExtraContext = mergeMapStringString(returnedConnectionContext.GetExtraContext(), requestedConnectionContext.GetExtraContext(), connectioncontext.GetExtraContext()) + rv.IpContext = requestedConnectionContext.GetIpContext() + rv.EthernetContext = requestedConnectionContext.GetEthernetContext() + rv.DnsContext = requestedConnectionContext.GetDnsContext() + rv.MTU = requestedConnectionContext.GetMTU() + rv.ExtraContext = mergeMapStringString(returnedConnectionContext.GetExtraContext(), requestedConnectionContext.GetExtraContext(), currentConnectionContext.GetExtraContext()) } return rv } -func mergeMapStringString(returnedMap, requestedMap, mapMap map[string]string) map[string]string { - // clone the map +func mergeMapStringString(returnedMap, requestedMap, currentMap map[string]string) map[string]string { + // clone the currentMap rv := make(map[string]string) - for k, v := range mapMap { + for k, v := range currentMap { rv[k] = v } + // Only intentional changes between the returnedMap (which was values last returned from calls to begin.Request(...)) + // and requestedMap (the values passed into begin.Request for this call) are considered for application to the existing + // map (currentMap - the last set of values remembered by the EventFactory). for k, v := range returnedMap { - requestedValue, ok := requestedMap[k] + srcValue, ok := requestedMap[k] // If a key is in returnedMap and its value differs from requestedMap, update the value - if ok && requestedValue != v { - rv[k] = requestedValue + if ok && srcValue != v { + rv[k] = srcValue } // If a key is in returnedMap and not in requestedMap, delete it if !ok { diff --git a/pkg/networkservice/common/begin/rerequest_client_test.go b/pkg/networkservice/common/begin/rerequest_client_test.go new file mode 100644 index 000000000..e45aead45 --- /dev/null +++ b/pkg/networkservice/common/begin/rerequest_client_test.go @@ -0,0 +1,81 @@ +// 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 begin_test + +import ( + "context" + "testing" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/kernel" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + "google.golang.org/protobuf/proto" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/chain" +) + +func TestReRequestClient(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := chain.NewNetworkServiceClient( + begin.NewClient(), + ) + + connOriginal, err := client.Request(ctx, &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{ + Id: "id", + }, + }) + + require.NoError(t, err) + require.NotNil(t, connOriginal) + + conn := connOriginal.Clone() + conn.Context = &networkservice.ConnectionContext{ + IpContext: &networkservice.IPContext{ + SrcIpAddrs: []string{"10.0.0.1/32"}, + }, + EthernetContext: &networkservice.EthernetContext{ + SrcMac: "00:00:00:00:00:00", + }, + DnsContext: &networkservice.DNSContext{ + Configs: []*networkservice.DNSConfig{ + { + DnsServerIps: []string{"1.1.1.1"}, + }, + }, + }, + ExtraContext: map[string]string{"foo": "bar"}, + } + conn.Mechanism = kernel.New("") + conn.Labels = map[string]string{"foo": "bar"} + + connReturned, err := client.Request(ctx, &networkservice.NetworkServiceRequest{ + Connection: conn, + }) + + require.NoError(t, err) + require.NotNil(t, connReturned) + require.Equal(t, connOriginal.GetMechanism(), connReturned.GetMechanism()) + require.True(t, proto.Equal(conn.GetContext(), connReturned.GetContext())) + require.Equal(t, connOriginal.GetLabels(), connReturned.GetLabels()) +} diff --git a/pkg/networkservice/common/cidr/cidr_test.go b/pkg/networkservice/common/cidr/cidr_test.go deleted file mode 100644 index a7c6f3b51..000000000 --- a/pkg/networkservice/common/cidr/cidr_test.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2021 Doc.ai 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 cidr_test - -import ( - "context" - "testing" - - "github.com/networkservicemesh/api/pkg/api/networkservice" - - "github.com/networkservicemesh/sdk/pkg/networkservice/common/cidr" - "github.com/networkservicemesh/sdk/pkg/networkservice/common/updatepath" - "github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters" - "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" - "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" - - "github.com/stretchr/testify/require" -) - -func newRequest() *networkservice.NetworkServiceRequest { - return &networkservice.NetworkServiceRequest{ - Connection: &networkservice.Connection{ - Context: &networkservice.ConnectionContext{ - IpContext: new(networkservice.IPContext), - }, - }, - } -} - -func newServer(prefixes, excludePrefixes []string) networkservice.NetworkServiceServer { - return next.NewNetworkServiceServer( - updatepath.NewServer("cidr-server"), - metadata.NewServer(), - cidr.NewServer(prefixes, excludePrefixes), - ) -} - -func newClient(prefixLen uint32, family networkservice.IpFamily_Family, server networkservice.NetworkServiceServer) networkservice.NetworkServiceClient { - return next.NewNetworkServiceClient( - updatepath.NewClient("cidr-client"), - metadata.NewClient(), - cidr.NewClient(prefixLen, family), - /* Server part */ - adapters.NewServerToClient( - server, - ), - ) -} - -func validateConn(t *testing.T, conn *networkservice.Connection, prefix, route string) { - require.Equal(t, conn.Context.IpContext.ExtraPrefixes[0], prefix) - require.Equal(t, conn.Context.IpContext.SrcRoutes, []*networkservice.Route{ - { - Prefix: route, - }, - }) -} - -func TestIPFamilies(t *testing.T) { - var samples = []struct { - name string - family networkservice.IpFamily_Family - prefixLen uint32 - prefix string - cidr0 string - cidr1 string - cidr2 string - }{ - { - name: "IPv4", - family: networkservice.IpFamily_IPV4, - prefix: "192.168.0.0/16", - prefixLen: 24, - cidr0: "192.168.0.0/24", - cidr1: "192.168.1.0/24", - cidr2: "192.168.2.0/24", - }, - { - name: "IPv6", - family: networkservice.IpFamily_IPV6, - prefix: "2001:db8::/96", - prefixLen: 112, - cidr0: "2001:db8::/112", - cidr1: "2001:db8::1:0/112", - cidr2: "2001:db8::2:0/112", - }, - } - - for _, sample := range samples { - t.Run(sample.name, func(t *testing.T) { - // nolint:scopelint - testIPFamilies(t, sample.family, sample.prefixLen, sample.prefix, sample.cidr0, sample.cidr1, sample.cidr2) - }) - } -} - -func testIPFamilies(t *testing.T, family networkservice.IpFamily_Family, prefixLen uint32, prefix, cidr0, cidr1, cidr2 string) { - prefixes := []string{prefix} - server := newServer(prefixes, nil) - - request := newRequest() - client1 := newClient(prefixLen, family, server) - conn1, err := client1.Request(context.Background(), request) - require.NoError(t, err) - validateConn(t, conn1, cidr0, prefix) - - // refresh - conn1, err = client1.Request(context.Background(), request) - require.NoError(t, err) - validateConn(t, conn1, cidr0, prefix) - - client2 := newClient(prefixLen, family, server) - conn2, err := client2.Request(context.Background(), newRequest()) - require.NoError(t, err) - validateConn(t, conn2, cidr1, prefix) - - _, err = client1.Close(context.Background(), conn1) - require.NoError(t, err) - - client3 := newClient(prefixLen, family, server) - conn3, err := client3.Request(context.Background(), newRequest()) - require.NoError(t, err) - validateConn(t, conn3, cidr0, prefix) - - client4 := newClient(prefixLen, family, server) - conn4, err := client4.Request(context.Background(), newRequest()) - require.NoError(t, err) - validateConn(t, conn4, cidr2, prefix) -} - -func TestIPv4Exclude(t *testing.T) { - prefixes := []string{"192.168.0.0/16"} - excludePrefixes := []string{"192.168.0.0/24"} - server := newServer(prefixes, excludePrefixes) - - client1 := newClient(24, networkservice.IpFamily_IPV4, server) - conn1, err := client1.Request(context.Background(), newRequest()) - require.NoError(t, err) - require.NotEqual(t, "192.168.0.0/24", conn1.Context.IpContext.ExtraPrefixes[0]) - require.Equal(t, conn1.Context.IpContext.SrcRoutes, []*networkservice.Route{ - { - Prefix: "192.168.0.0/16", - }, - }) -} diff --git a/pkg/networkservice/common/cidr/client.go b/pkg/networkservice/common/cidr/client.go deleted file mode 100644 index bddd0eff2..000000000 --- a/pkg/networkservice/common/cidr/client.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2021 Doc.ai 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 cidr - -import ( - "context" - - "github.com/golang/protobuf/ptypes/empty" - "github.com/networkservicemesh/api/pkg/api/networkservice" - "google.golang.org/grpc" - - "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" - "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" -) - -type cidrClient struct { - prefixLen uint32 - family networkservice.IpFamily_Family -} - -// NewClient creates a NetworkServiceClient chain element that requests ExtraPrefix -func NewClient(prefixLen uint32, family networkservice.IpFamily_Family) networkservice.NetworkServiceClient { - return &cidrClient{ - prefixLen: prefixLen, - family: family, - } -} - -func (c *cidrClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { - conn := request.GetConnection() - if conn.GetContext() == nil { - conn.Context = &networkservice.ConnectionContext{} - } - if conn.GetContext().GetIpContext() == nil { - conn.Context.IpContext = &networkservice.IPContext{} - } - ipContext := conn.GetContext().GetIpContext() - - // Add ExtraPrefixRequest if there is no extra prefix - if ipContext.GetExtraPrefixes() == nil { - ipContext.ExtraPrefixRequest = append(ipContext.ExtraPrefixRequest, &networkservice.ExtraPrefixRequest{ - AddrFamily: &networkservice.IpFamily{Family: c.family}, - PrefixLen: c.prefixLen, - RequiredNumber: 1, - RequestedNumber: 1, - }) - } - - // Add ExtraPrefix to the route for the remote side - loaded := load(ctx, metadata.IsClient(c)) - if !loaded && ipContext.GetExtraPrefixes() != nil { - for _, item := range ipContext.ExtraPrefixes { - ipContext.DstRoutes = append(ipContext.DstRoutes, &networkservice.Route{ - Prefix: item, - }) - } - store(ctx, metadata.IsClient(c)) - } - - conn, err := next.Client(ctx).Request(ctx, request, opts...) - if err != nil && !loaded { - delete(ctx, metadata.IsClient(c)) - } - - return conn, err -} - -func (c *cidrClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { - delete(ctx, metadata.IsClient(c)) - return next.Client(ctx).Close(ctx, conn, opts...) -} diff --git a/pkg/networkservice/common/cidr/metadata.go b/pkg/networkservice/common/cidr/metadata.go deleted file mode 100644 index d8a14d8e6..000000000 --- a/pkg/networkservice/common/cidr/metadata.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2021 Doc.ai 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 cidr - -import ( - "context" - - "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" -) - -type key struct{} - -func store(ctx context.Context, isClient bool) { - metadata.Map(ctx, isClient).Store(key{}, struct{}{}) -} - -func delete(ctx context.Context, isClient bool) { - metadata.Map(ctx, isClient).Delete(key{}) -} - -func load(ctx context.Context, isClient bool) (ok bool) { - _, ok = metadata.Map(ctx, isClient).Load(key{}) - return ok -} diff --git a/pkg/networkservice/common/cidr/server.go b/pkg/networkservice/common/cidr/server.go deleted file mode 100644 index d5ca59af5..000000000 --- a/pkg/networkservice/common/cidr/server.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2021 Doc.ai 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 cidr - -import ( - "context" - "sync" - - "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" - - "github.com/networkservicemesh/api/pkg/api/networkservice" - - "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" - "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" - "github.com/networkservicemesh/sdk/pkg/tools/prefixpool" -) - -type cidrServer struct { - pool *prefixpool.PrefixPool - - prefixes []string - excludePrefixes []string - once sync.Once - initErr error -} - -// NewServer creates a NetworkServiceServer chain element that allocates CIDR from some global prefix -// and saves it in ExtraPrefix -func NewServer(prefixes, excludePrefixes []string) networkservice.NetworkServiceServer { - return &cidrServer{ - prefixes: prefixes, - excludePrefixes: excludePrefixes, - } -} - -func (c *cidrServer) init() { - if len(c.prefixes) == 0 { - c.initErr = errors.New("required one or more prefixes") - return - } - var err error - if c.pool, err = prefixpool.New(c.prefixes...); err != nil { - c.initErr = errors.New("required one or more prefixes") - return - } - if _, err = c.pool.ExcludePrefixes(c.excludePrefixes); err != nil { - c.initErr = errors.New("unable to exclude additional prefixes") - return - } -} - -func (c *cidrServer) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { - c.once.Do(c.init) - if c.initErr != nil { - return nil, c.initErr - } - - conn := request.GetConnection() - if conn.GetContext() == nil { - conn.Context = &networkservice.ConnectionContext{} - } - if conn.GetContext().GetIpContext() == nil { - conn.Context.IpContext = &networkservice.IPContext{} - } - ipContext := conn.GetContext().GetIpContext() - - /* If we already have extra prefixes, exclude them from the next CIDR allocations */ - if ipContext.GetExtraPrefixes() != nil { - if _, err := c.pool.ExcludePrefixes(ipContext.GetExtraPrefixes()); err != nil { - return nil, err - } - } else if ipContext.GetExtraPrefixRequest() != nil { - /* Else, extract a new one if there is ExtraPrefixRequest */ - requested, err := c.pool.ExtractPrefixes(request.GetConnection().GetId(), ipContext.GetExtraPrefixRequest()...) - if err != nil { - return nil, err - } - ipContext.ExtraPrefixes = append(ipContext.ExtraPrefixes, requested...) - ipContext.ExtraPrefixRequest = nil - - for i := 0; i < len(requested); i++ { - ipContext.DstRoutes = append(ipContext.DstRoutes, &networkservice.Route{ - Prefix: requested[i], - }) - } - } - - conn, err := next.Server(ctx).Request(ctx, request) - if err != nil { - extraPrefixes := request.GetConnection().GetContext().GetIpContext().GetExtraPrefixes() - if len(extraPrefixes) != 0 { - _ = c.pool.ReleaseExcludedPrefixes(extraPrefixes) - } - return conn, err - } - - ipContext = conn.GetContext().GetIpContext() - if ok := load(ctx, metadata.IsClient(c)); !ok { - /* Set srcRoutes = prefixes because these hosts should be available through this element */ - for i := 0; i < len(c.prefixes); i++ { - ipContext.SrcRoutes = append(ipContext.SrcRoutes, &networkservice.Route{ - Prefix: c.prefixes[i], - }) - } - store(ctx, metadata.IsClient(c)) - } - - return conn, err -} - -func (c *cidrServer) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { - extraPrefixes := conn.GetContext().GetIpContext().GetExtraPrefixes() - if len(extraPrefixes) != 0 { - _ = c.pool.ReleaseExcludedPrefixes(extraPrefixes) - } - delete(ctx, metadata.IsClient(c)) - - return next.Server(ctx).Close(ctx, conn) -} diff --git a/pkg/networkservice/common/cleanup/cleanup_test.go b/pkg/networkservice/common/cleanup/cleanup_test.go new file mode 100644 index 000000000..2fa6ce32f --- /dev/null +++ b/pkg/networkservice/common/cleanup/cleanup_test.go @@ -0,0 +1,120 @@ +// 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 cleanup_test + +import ( + "context" + "fmt" + "testing" + "time" + + "go.uber.org/goleak" + + "github.com/stretchr/testify/require" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/common/cleanup" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/chain" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/count" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +func TestCleanUp_CtxDone(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + chainCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + + counter := new(count.Client) + + client := chain.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + cleanup.NewClient(chainCtx), + counter, + ) + req := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{Id: "nsc-1"}, + } + _, err := client.Request(context.Background(), req) + require.NoError(t, err) + require.Equal(t, 1, counter.Requests()) + require.Equal(t, 0, counter.Closes()) + cancel() + + require.Eventually(t, func() bool { + return counter.Closes() == 1 + }, time.Millisecond*100, time.Millisecond*10) +} + +func TestCleanUp_Close(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + chainCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + + counter := new(count.Client) + + client := chain.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + cleanup.NewClient(chainCtx), + counter, + ) + req := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{Id: "nsc-1"}, + } + conn, err := client.Request(context.Background(), req) + require.NoError(t, err) + + _, _ = client.Close(context.Background(), conn) + require.Equal(t, 1, counter.Closes()) + cancel() + require.Never(t, func() bool { + return counter.Closes() > 1 + }, time.Millisecond*100, time.Millisecond*10) +} + +func TestCleanUp_Chan(t *testing.T) { + t.Cleanup(func() { goleak.VerifyNone(t) }) + chainCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + + counter := new(count.Client) + + doneCh := make(chan struct{}) + client := chain.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + cleanup.NewClient(chainCtx, cleanup.WithDoneChan(doneCh)), + counter, + ) + + requestsNumber := 500 + for i := 0; i < requestsNumber; i++ { + req := &networkservice.NetworkServiceRequest{ + Connection: &networkservice.Connection{Id: fmt.Sprintf("nsc-%v", i)}, + } + _, err := client.Request(context.Background(), req) + require.NoError(t, err) + } + + cancel() + <-doneCh + + require.Equal(t, counter.Closes(), requestsNumber) +} diff --git a/pkg/networkservice/common/cleanup/client.go b/pkg/networkservice/common/cleanup/client.go new file mode 100644 index 000000000..5a8ed2a7c --- /dev/null +++ b/pkg/networkservice/common/cleanup/client.go @@ -0,0 +1,124 @@ +// 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 cleanup + +import ( + "context" + "sync/atomic" + + "github.com/edwarnicke/serialize" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/common/clientconn" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" +) + +type cleanupClient struct { + chainCtx context.Context + + ccClose bool + doneCh chan struct{} + activeConns int32 + executor serialize.Executor +} + +// NewClient - returns a cleanup client chain element +func NewClient(ctx context.Context, opts ...Option) networkservice.NetworkServiceClient { + o := &options{} + for _, opt := range opts { + opt(o) + } + c := &cleanupClient{ + chainCtx: ctx, + ccClose: o.ccClose, + doneCh: o.doneCh, + } + go func() { + <-c.chainCtx.Done() + if atomic.LoadInt32(&c.activeConns) == 0 && c.doneCh != nil { + c.executor.AsyncExec(func() { + select { + case <-c.doneCh: + default: + close(c.doneCh) + } + }) + } + }() + return c +} + +func (c *cleanupClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { + conn, err := next.Client(ctx).Request(ctx, request, opts...) + if err != nil { + return nil, err + } + // Update active connections counter. Needed for a cleanup done notification. + atomic.AddInt32(&c.activeConns, 1) + if cancel, ok := loadAndDeleteCancel(ctx); ok { + cancel() + } + + cancelCtx, cancel := context.WithCancel(context.Background()) + storeCancel(ctx, cancel) + + factory := begin.FromContext(ctx) + go func() { + select { + case <-c.chainCtx.Done(): + // Add to metadata if we want to delete clientconn + if c.ccClose { + storeCC(ctx) + } + + <-factory.Close(begin.CancelContext(cancelCtx)) + atomic.AddInt32(&c.activeConns, -1) + + if atomic.LoadInt32(&c.activeConns) == 0 && c.doneCh != nil { + c.executor.AsyncExec(func() { + select { + case <-c.doneCh: + default: + close(c.doneCh) + } + }) + } + case <-cancelCtx.Done(): + atomic.AddInt32(&c.activeConns, -1) + } + }() + return conn, err +} + +func (c *cleanupClient) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { + if cancel, ok := loadAndDeleteCancel(ctx); ok { + if _, ok := loadAndDeleteCC(ctx); ok { + if cc, ok := clientconn.Load(ctx); ok { + if closable, ok := cc.(interface{ Close() error }); ok { + _ = closable.Close() + } + clientconn.Delete(ctx) + } + } + cancel() + } + return next.Client(ctx).Close(ctx, conn, opts...) +} diff --git a/pkg/networkservice/common/cidr/doc.go b/pkg/networkservice/common/cleanup/doc.go similarity index 76% rename from pkg/networkservice/common/cidr/doc.go rename to pkg/networkservice/common/cleanup/doc.go index 15d2a7661..e7088bf7b 100644 --- a/pkg/networkservice/common/cidr/doc.go +++ b/pkg/networkservice/common/cleanup/doc.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,5 +14,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package cidr provides networkservice.NetworkService chain elements for creating loopback interface -package cidr +// Package cleanup provides networkservice.NetworkService chain elements to clean up resources before termination +package cleanup diff --git a/pkg/networkservice/common/cleanup/metadata.go b/pkg/networkservice/common/cleanup/metadata.go new file mode 100644 index 000000000..b79e4c3ae --- /dev/null +++ b/pkg/networkservice/common/cleanup/metadata.go @@ -0,0 +1,58 @@ +// 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 cleanup + +import ( + "context" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type keyCancel struct{} +type keyCC struct{} + +// storeCancel sets the context.CancelFunc stored in per Connection.Id metadata. +func storeCancel(ctx context.Context, cancel context.CancelFunc) { + metadata.Map(ctx, true).Store(keyCancel{}, cancel) +} + +// loadAndDeleteCancel deletes the context.CancelFunc stored in per Connection.Id metadata, +// returning the previous value if any. The loaded result reports whether the key was present. +func loadAndDeleteCancel(ctx context.Context) (value context.CancelFunc, ok bool) { + rawValue, ok := metadata.Map(ctx, true).LoadAndDelete(keyCancel{}) + if !ok { + return + } + value, ok = rawValue.(context.CancelFunc) + return value, ok +} + +// storeCC sets the flag to delete clientconn in per Connection.Id metadata. +func storeCC(ctx context.Context) { + metadata.Map(ctx, true).Store(keyCC{}, struct{}{}) +} + +// loadAndDeleteCC deletes the flag stored in per Connection.Id metadata, +// returning the previous value if any. The loaded result reports whether the key was present. +func loadAndDeleteCC(ctx context.Context) (value struct{}, ok bool) { + rawValue, ok := metadata.Map(ctx, true).LoadAndDelete(keyCC{}) + if !ok { + return + } + value, ok = rawValue.(struct{}) + return value, ok +} diff --git a/pkg/tools/fs/inode_windows_amd64.go b/pkg/networkservice/common/cleanup/options.go similarity index 52% rename from pkg/tools/fs/inode_windows_amd64.go rename to pkg/networkservice/common/cleanup/options.go index 5d919222d..5514a762f 100644 --- a/pkg/tools/fs/inode_windows_amd64.go +++ b/pkg/networkservice/common/cleanup/options.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Cisco and/or its affiliates. +// Copyright (c) 2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,27 +14,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package fs provides common filesystem functions and utilities -package fs +package cleanup -import ( - "os" - "syscall" - "unsafe" +type options struct { + ccClose bool + doneCh chan struct{} +} - "github.com/pkg/errors" -) +// Option - options for the cleanup chain element +type Option func(*options) -// GetInode returns Inode for file -func GetInode(file string) (uintptr, error) { - fileInfo, err := os.Stat(file) - if err != nil { - return 0, errors.Wrap(err, "error stat file") +// WithoutGRPCCall - closes client connection to prevent calling requests/closes on other endpoints +func WithoutGRPCCall() Option { + return func(o *options) { + o.ccClose = true } - stat, ok := fileInfo.Sys().(*syscall.Handle) - if !ok { - return 0, errors.New("not a syscall.Handle") +} + +// WithDoneChan - receives a channel to notify the end of cleaning +func WithDoneChan(doneCh chan struct{}) Option { + return func(o *options) { + o.doneCh = doneCh } - ptr := uintptr(unsafe.Pointer(stat)) - return ptr, nil } diff --git a/pkg/networkservice/common/discover/match_selector.go b/pkg/networkservice/common/discover/match_selector.go index e85393450..c5e62f78d 100644 --- a/pkg/networkservice/common/discover/match_selector.go +++ b/pkg/networkservice/common/discover/match_selector.go @@ -1,6 +1,6 @@ -// Copyright (c) 2018-2020 VMware, Inc. +// Copyright (c) 2018-2022 VMware, Inc. // -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -26,13 +26,7 @@ import ( ) func matchEndpoint(clockTime clock.Clock, nsLabels map[string]string, ns *registry.NetworkService, nses ...*registry.NetworkServiceEndpoint) []*registry.NetworkServiceEndpoint { - var validNetworkServiceEndpoints []*registry.NetworkServiceEndpoint - for _, nse := range nses { - if nse.GetExpirationTime() == nil || nse.GetExpirationTime().AsTime().After(clockTime.Now()) { - validNetworkServiceEndpoints = append(validNetworkServiceEndpoints, nse) - } - } - + validNetworkServiceEndpoints := validateExpirationTime(clockTime, nses) // Iterate through the matches for _, match := range ns.GetMatches() { // All match source selector labels should be present in the requested labels map @@ -68,3 +62,14 @@ func matchEndpoint(clockTime clock.Clock, nsLabels map[string]string, ns *regist return validNetworkServiceEndpoints } + +func validateExpirationTime(clockTime clock.Clock, nses []*registry.NetworkServiceEndpoint) []*registry.NetworkServiceEndpoint { + var validNetworkServiceEndpoints []*registry.NetworkServiceEndpoint + for _, nse := range nses { + if nse.GetExpirationTime() == nil || nse.GetExpirationTime().AsTime().After(clockTime.Now()) { + validNetworkServiceEndpoints = append(validNetworkServiceEndpoints, nse) + } + } + + return validNetworkServiceEndpoints +} diff --git a/pkg/networkservice/common/discover/server.go b/pkg/networkservice/common/discover/server.go index f2cc71c10..89b3a22d4 100644 --- a/pkg/networkservice/common/discover/server.go +++ b/pkg/networkservice/common/discover/server.go @@ -1,6 +1,6 @@ -// Copyright (c) 2020-2021 Cisco Systems, Inc. +// Copyright (c) 2020-2022 Cisco Systems, Inc. // -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -128,7 +128,7 @@ func (d *discoverCandidatesServer) discoverNetworkServiceEndpoint(ctx context.Co return nil, errors.Errorf("network service endpoint %v not found", nseName) } -func (d *discoverCandidatesServer) discoverNetworkServiceEndpoints(ctx context.Context, ns *registry.NetworkService, labels map[string]string) ([]*registry.NetworkServiceEndpoint, error) { +func (d *discoverCandidatesServer) discoverNetworkServiceEndpoints(ctx context.Context, ns *registry.NetworkService, nsLabels map[string]string) ([]*registry.NetworkServiceEndpoint, error) { clockTime := clock.FromContext(ctx) query := ®istry.NetworkServiceEndpointQuery{ @@ -143,7 +143,7 @@ func (d *discoverCandidatesServer) discoverNetworkServiceEndpoints(ctx context.C } nseList := registry.ReadNetworkServiceEndpointList(nseRespStream) - result := matchEndpoint(clockTime, labels, ns, nseList...) + result := matchEndpoint(clockTime, nsLabels, ns, nseList...) if len(result) != 0 { return result, nil } diff --git a/pkg/networkservice/common/discover/server_test.go b/pkg/networkservice/common/discover/server_test.go index dc189c84a..4d3055121 100644 --- a/pkg/networkservice/common/discover/server_test.go +++ b/pkg/networkservice/common/discover/server_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -270,6 +270,7 @@ func TestDiscoverCandidatesServer_MatchEmptySourceSelectorGoingFirst(t *testing. } func TestDiscoverCandidatesServer_MatchNothing(t *testing.T) { + t.Skip() t.Cleanup(func() { goleak.VerifyNone(t) }) ctx, cancel := context.WithTimeout(context.Background(), testWait) diff --git a/pkg/networkservice/common/discoverforwarder/server.go b/pkg/networkservice/common/discoverforwarder/server.go index 4077a6b55..68635fae5 100644 --- a/pkg/networkservice/common/discoverforwarder/server.go +++ b/pkg/networkservice/common/discoverforwarder/server.go @@ -93,6 +93,17 @@ func (d *discoverForwarderServer) Request(ctx context.Context, request *networks return nil, errors.New("no candidates found") } + segments := request.Connection.GetPath().GetPathSegments() + if pathIndex := int(request.Connection.GetPath().Index); len(segments) > pathIndex+1 { + datapathForwarder := segments[pathIndex+1].Name + for i, candidate := range nses { + if candidate.Name == datapathForwarder { + nses[0], nses[i] = nses[i], nses[0] + break + } + } + } + var candidatesErr = errors.New("all forwarders have failed") // TODO: Should we consider about load balancing? diff --git a/pkg/networkservice/common/excludedprefixes/client.go b/pkg/networkservice/common/excludedprefixes/client.go index 64aa51701..3303a7dc6 100644 --- a/pkg/networkservice/common/excludedprefixes/client.go +++ b/pkg/networkservice/common/excludedprefixes/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -19,10 +19,14 @@ package excludedprefixes import ( "context" "net" + "net/url" + "strings" + "sync/atomic" "github.com/edwarnicke/serialize" "github.com/golang/protobuf/ptypes/empty" "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/networkservicemesh/api/pkg/api/networkservice/mechanisms/common" "github.com/pkg/errors" "google.golang.org/grpc" @@ -32,16 +36,35 @@ import ( "github.com/networkservicemesh/sdk/pkg/tools/postpone" ) +type awarenessGroup struct { + NSUrlSet map[url.URL]struct{} + ExcludedPrfixes []string + ConnectionCounter int32 +} + +func (g *awarenessGroup) contains(nsURL *url.URL) bool { + _, ok := g.NSUrlSet[*nsURL] + return ok +} + type excludedPrefixesClient struct { excludedPrefixes []string + awarenessGroups []*awarenessGroup executor serialize.Executor } // NewClient - creates a networkservice.NetworkServiceClient chain element that excludes prefixes already used by other NetworkServices -func NewClient() networkservice.NetworkServiceClient { - return &excludedPrefixesClient{ +func NewClient(opts ...ClientOption) networkservice.NetworkServiceClient { + client := &excludedPrefixesClient{ excludedPrefixes: make([]string, 0), + awarenessGroups: make([]*awarenessGroup, 0), } + + for _, opt := range opts { + opt(client) + } + + return client } func (epc *excludedPrefixesClient) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { @@ -54,16 +77,27 @@ func (epc *excludedPrefixesClient) Request(ctx context.Context, request *network conn.Context.IpContext = &networkservice.IPContext{} } + nsurl := getNSURL(request) + groupIndex := checkAwarenessGroups(nsurl, epc.awarenessGroups) + + var awarenessGroupsExcludedPrefixes []string + for i, group := range epc.awarenessGroups { + if i != groupIndex { + awarenessGroupsExcludedPrefixes = append(awarenessGroupsExcludedPrefixes, group.ExcludedPrfixes...) + } + } + logger := log.FromContext(ctx).WithField("ExcludedPrefixesClient", "Request") ipCtx := conn.GetContext().GetIpContext() var newExcludedPrefixes []string oldExcludedPrefixes := ipCtx.GetExcludedPrefixes() - if len(epc.excludedPrefixes) > 0 { + if len(epc.excludedPrefixes) > 0 || len(awarenessGroupsExcludedPrefixes) > 0 { <-epc.executor.AsyncExec(func() { logger.Debugf("Adding new excluded IPs to the request: %+v", epc.excludedPrefixes) newExcludedPrefixes = ipCtx.GetExcludedPrefixes() newExcludedPrefixes = append(newExcludedPrefixes, epc.excludedPrefixes...) + newExcludedPrefixes = append(newExcludedPrefixes, awarenessGroupsExcludedPrefixes...) newExcludedPrefixes = removeDuplicates(newExcludedPrefixes) // excluding IPs for current request/connection before calling next client for the refresh use-case @@ -103,12 +137,22 @@ func (epc *excludedPrefixesClient) Request(ctx context.Context, request *network respIPContext.GetDstIpAddrs(), respIPContext.GetExcludedPrefixes()) <-epc.executor.AsyncExec(func() { - epc.excludedPrefixes = append(epc.excludedPrefixes, respIPContext.GetSrcIpAddrs()...) - epc.excludedPrefixes = append(epc.excludedPrefixes, respIPContext.GetDstIpAddrs()...) - epc.excludedPrefixes = append(epc.excludedPrefixes, getRoutePrefixes(respIPContext.GetSrcRoutes())...) - epc.excludedPrefixes = append(epc.excludedPrefixes, getRoutePrefixes(respIPContext.GetDstRoutes())...) - epc.excludedPrefixes = append(epc.excludedPrefixes, respIPContext.GetExcludedPrefixes()...) - epc.excludedPrefixes = removeDuplicates(epc.excludedPrefixes) + var excludedPrefixes []string + excludedPrefixes = append(excludedPrefixes, respIPContext.GetSrcIpAddrs()...) + excludedPrefixes = append(excludedPrefixes, respIPContext.GetDstIpAddrs()...) + excludedPrefixes = append(excludedPrefixes, getRoutePrefixes(respIPContext.GetSrcRoutes())...) + excludedPrefixes = append(excludedPrefixes, getRoutePrefixes(respIPContext.GetDstRoutes())...) + + if groupIndex >= 0 { + epc.awarenessGroups[groupIndex].ExcludedPrfixes = append(epc.awarenessGroups[groupIndex].ExcludedPrfixes, excludedPrefixes...) + epc.awarenessGroups[groupIndex].ExcludedPrfixes = removeDuplicates(epc.awarenessGroups[groupIndex].ExcludedPrfixes) + atomic.AddInt32(&epc.awarenessGroups[groupIndex].ConnectionCounter, 1) + } else { + excludedPrefixes = append(excludedPrefixes, respIPContext.GetExcludedPrefixes()...) + epc.excludedPrefixes = append(epc.excludedPrefixes, excludedPrefixes...) + epc.excludedPrefixes = removeDuplicates(epc.excludedPrefixes) + } + logger.Debugf("Added excluded prefixes: %+v", epc.excludedPrefixes) }) @@ -127,6 +171,16 @@ func (epc *excludedPrefixesClient) Close(ctx context.Context, conn *networkservi epc.excludedPrefixes = exclude(epc.excludedPrefixes, getRoutePrefixes(ipCtx.GetSrcRoutes())) epc.excludedPrefixes = exclude(epc.excludedPrefixes, getRoutePrefixes(ipCtx.GetDstRoutes())) epc.excludedPrefixes = exclude(epc.excludedPrefixes, ipCtx.GetExcludedPrefixes()) + + nsurl := getNSURL(&networkservice.NetworkServiceRequest{Connection: conn}) + groupIndex := checkAwarenessGroups(nsurl, epc.awarenessGroups) + if groupIndex >= 0 { + atomic.AddInt32(&epc.awarenessGroups[groupIndex].ConnectionCounter, -1) + if atomic.LoadInt32(&epc.awarenessGroups[groupIndex].ConnectionCounter) == 0 { + epc.awarenessGroups[groupIndex].ExcludedPrfixes = make([]string, 0) + } + } + logger.Debugf("Excluded prefixes after closing connection: %+v", epc.excludedPrefixes) }) @@ -173,3 +227,36 @@ func validateIPs(ipContext *networkservice.IPContext, excludedPrefixes []string) return nil } + +func getNSURL(request *networkservice.NetworkServiceRequest) *url.URL { + nsurl := &url.URL{} + + nsurl.Host = request.GetConnection().GetNetworkService() + mechanism := request.GetConnection().GetMechanism() + if mechanism == nil && len(request.MechanismPreferences) > 0 { + mechanism = request.MechanismPreferences[0] + } + + nsurl.Scheme = strings.ToLower(mechanism.GetType()) + iface := mechanism.GetParameters()[common.InterfaceNameKey] + if iface != "" { + nsurl.Path = "/" + iface + } + query := nsurl.Query() + for k, v := range request.GetConnection().GetLabels() { + query.Add(k, v) + } + nsurl.RawQuery = query.Encode() + + return nsurl +} + +func checkAwarenessGroups(nsurl *url.URL, awarenessGroups []*awarenessGroup) int { + for i, group := range awarenessGroups { + if group.contains(nsurl) { + return i + } + } + + return -1 +} diff --git a/pkg/networkservice/common/excludedprefixes/options.go b/pkg/networkservice/common/excludedprefixes/options.go index 20a1e072a..1020a3009 100644 --- a/pkg/networkservice/common/excludedprefixes/options.go +++ b/pkg/networkservice/common/excludedprefixes/options.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,6 +16,8 @@ package excludedprefixes +import "net/url" + // ServerOption - method for excludedPrefixesServer type ServerOption func(server *excludedPrefixesServer) @@ -25,3 +27,23 @@ func WithConfigPath(s string) ServerOption { args.configPath = s } } + +// ClientOption - method for excludedPrefixesClient +type ClientOption func(client *excludedPrefixesClient) + +// WithAwarenessGroups - returns method that sets awarenessGroups in excludedPrefixesClient +func WithAwarenessGroups(awarenessGroups [][]*url.URL) ClientOption { + return func(args *excludedPrefixesClient) { + groups := make([]*awarenessGroup, len(awarenessGroups)) + for i, g := range awarenessGroups { + groups[i] = &awarenessGroup{ + NSUrlSet: make(map[url.URL]struct{}), + ExcludedPrfixes: make([]string, 0), + } + for _, item := range g { + groups[i].NSUrlSet[*item] = struct{}{} + } + } + args.awarenessGroups = groups + } +} diff --git a/pkg/networkservice/common/mechanisms/recvfd/client_notlinux.go b/pkg/networkservice/common/mechanisms/recvfd/client_notlinux.go index aaf6d38bb..fd1c01459 100644 --- a/pkg/networkservice/common/mechanisms/recvfd/client_notlinux.go +++ b/pkg/networkservice/common/mechanisms/recvfd/client_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package recvfd diff --git a/pkg/networkservice/common/mechanisms/recvfd/server_notlinux.go b/pkg/networkservice/common/mechanisms/recvfd/server_notlinux.go index b1299f32d..b99f46c0c 100644 --- a/pkg/networkservice/common/mechanisms/recvfd/server_notlinux.go +++ b/pkg/networkservice/common/mechanisms/recvfd/server_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package recvfd diff --git a/pkg/networkservice/common/mechanisms/sendfd/client_notlinux.go b/pkg/networkservice/common/mechanisms/sendfd/client_notlinux.go index 399e46d5e..ecdc7ccae 100644 --- a/pkg/networkservice/common/mechanisms/sendfd/client_notlinux.go +++ b/pkg/networkservice/common/mechanisms/sendfd/client_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package sendfd diff --git a/pkg/networkservice/common/mechanisms/sendfd/server_notlinux.go b/pkg/networkservice/common/mechanisms/sendfd/server_notlinux.go index 5a98517d9..ab5280a20 100644 --- a/pkg/networkservice/common/mechanisms/sendfd/server_notlinux.go +++ b/pkg/networkservice/common/mechanisms/sendfd/server_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package sendfd diff --git a/pkg/networkservice/common/policyroute/server_test.go b/pkg/networkservice/common/policyroute/server_test.go index a30fff66d..4f11301fd 100644 --- a/pkg/networkservice/common/policyroute/server_test.go +++ b/pkg/networkservice/common/policyroute/server_test.go @@ -90,12 +90,12 @@ func TestCheckReloadedPolicies(t *testing.T) { defer cancel() // Create policies policies := []*networkservice.PolicyRoute{ - {From: "172.16.2.201/24", Proto: 6, Port: 6666, Routes: []*networkservice.Route{{ + {From: "172.16.2.201/24", Proto: "6", DstPort: "6666", Routes: []*networkservice.Route{{ Prefix: "172.16.3.0/24", NextHop: "172.16.2.200", }}}, - {Proto: 17, Port: 6666}, - {Proto: 17, Port: 5555, Routes: []*networkservice.Route{{ + {Proto: "17", DstPort: "6666"}, + {Proto: "17", DstPort: "5555", Routes: []*networkservice.Route{{ Prefix: "2004::5/120", NextHop: "2004::6", }}}, diff --git a/pkg/networkservice/connectioncontext/ipcontext/vl3/client.go b/pkg/networkservice/connectioncontext/ipcontext/vl3/client.go new file mode 100644 index 000000000..52be2bd99 --- /dev/null +++ b/pkg/networkservice/connectioncontext/ipcontext/vl3/client.go @@ -0,0 +1,147 @@ +// 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 vl3 provides chain elements that manage ipcontext of request for vL3 networks. +// Depends on `begin`, `metadata` chain elements. +package vl3 + +import ( + "context" + "errors" + + "github.com/edwarnicke/serialize" + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/ipam" + "github.com/networkservicemesh/api/pkg/api/networkservice" + "google.golang.org/grpc" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" +) + +type vl3Client struct { + pool vl3IPAM + chainContext context.Context + executor serialize.Executor + subscriptions []chan struct{} +} + +// NewClient - returns a new vL3 client instance that manages connection.context.ipcontext for vL3 scenario. +// Produces refresh on prefix update. +// Requires begin and metdata chain elements. +func NewClient(chainContext context.Context, prefixCh <-chan *ipam.PrefixResponse) networkservice.NetworkServiceClient { + if chainContext == nil { + panic("chainContext can not be nil") + } + if prefixCh == nil { + panic("prefixCh can not be nil") + } + var r = &vl3Client{ + chainContext: chainContext, + } + + go func() { + for update := range prefixCh { + prefixResp := update + r.executor.AsyncExec(func() { + r.pool.reset(chainContext, prefixResp.GetPrefix(), prefixResp.GetExcludePrefixes()) + for _, sub := range r.subscriptions { + sub <- struct{}{} + } + }) + } + }() + + return r +} + +func (n *vl3Client) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { + if !n.pool.isInitialized() { + return nil, errors.New("prefix pool is initializing") + } + eventFactory := begin.FromContext(ctx) + if eventFactory == nil { + return nil, errors.New("begin is required. Please add begin.NewClient() into chain") + } + cancelCtx, cancel := context.WithCancel(n.chainContext) + + if oldCancel, loaded := loadAndDeleteCancel(ctx); loaded { + oldCancel() + } + + storeCancel(ctx, cancel) + + notifyCh := make(chan struct{}) + + n.executor.AsyncExec(func() { + n.subscriptions = append(n.subscriptions, notifyCh) + }) + + go func() { + defer func() { + n.executor.AsyncExec(func() { + for i, sub := range n.subscriptions { + if sub == notifyCh { + n.subscriptions = append(n.subscriptions[:i], n.subscriptions[i+1:]...) + close(notifyCh) + return + } + } + }) + }() + + select { + case <-n.chainContext.Done(): + return + case <-cancelCtx.Done(): + return + case <-notifyCh: + eventFactory.Request(begin.CancelContext(cancelCtx)) + } + }() + + if request.Connection == nil { + request.Connection = new(networkservice.Connection) + } + var conn = request.GetConnection() + if conn.GetContext() == nil { + conn.Context = new(networkservice.ConnectionContext) + } + if conn.GetContext().GetIpContext() == nil { + conn.GetContext().IpContext = new(networkservice.IPContext) + } + + var address, prefix = n.pool.selfAddress().String(), n.pool.selfPrefix().String() + + conn.GetContext().GetIpContext().SrcIpAddrs = []string{address} + conn.GetContext().GetIpContext().DstRoutes = []*networkservice.Route{ + { + Prefix: address, + }, + { + Prefix: prefix, + }, + } + + return next.Client(ctx).Request(ctx, request, opts...) +} + +func (n *vl3Client) Close(ctx context.Context, conn *networkservice.Connection, opts ...grpc.CallOption) (*empty.Empty, error) { + if oldCancel, loaded := loadAndDeleteCancel(ctx); loaded { + oldCancel() + } + return next.Client(ctx).Close(ctx, conn, opts...) +} diff --git a/pkg/networkservice/connectioncontext/ipcontext/vl3/client_test.go b/pkg/networkservice/connectioncontext/ipcontext/vl3/client_test.go new file mode 100644 index 000000000..5ab484045 --- /dev/null +++ b/pkg/networkservice/connectioncontext/ipcontext/vl3/client_test.go @@ -0,0 +1,209 @@ +// 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 vl3_test + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + "github.com/networkservicemesh/api/pkg/api/ipam" + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/begin" + "github.com/networkservicemesh/sdk/pkg/networkservice/connectioncontext/ipcontext/vl3" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +func Test_VL3NSE_ConnectsToVl3NSE(t *testing.T) { + t.Cleanup(func() { + goleak.VerifyNone(t) + }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + var serverPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(serverPrefixCh) + + serverPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + var clientPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(clientPrefixCh) + + clientPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.1.0/24"} + + var server = next.NewNetworkServiceServer( + adapters.NewClientToServer( + next.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + vl3.NewClient(ctx, clientPrefixCh), + ), + ), + metadata.NewServer(), + vl3.NewServer(ctx, serverPrefixCh), + ) + + require.Eventually(t, func() bool { return len(serverPrefixCh) == 0 && len(clientPrefixCh) == 0 }, time.Second, time.Millisecond*100) + + resp, err := server.Request(ctx, &networkservice.NetworkServiceRequest{Connection: &networkservice.Connection{Id: t.Name()}}) + + require.NoError(t, err) + + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.1.0/24", resp.GetContext().GetIpContext().GetDstRoutes()[1].GetPrefix()) + + // refresh + resp, err = server.Request(ctx, &networkservice.NetworkServiceRequest{Connection: resp}) + + require.NoError(t, err) + + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.1.0/24", resp.GetContext().GetIpContext().GetDstRoutes()[1].GetPrefix()) +} + +func Test_VL3NSE_ConnectsToVl3NSE_ChangePrefix(t *testing.T) { + t.Cleanup(func() { + goleak.VerifyNone(t) + }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + var serverPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(serverPrefixCh) + + serverPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + var clientPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(clientPrefixCh) + + clientPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.1.0/24"} + + var server = next.NewNetworkServiceServer( + adapters.NewClientToServer( + next.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + vl3.NewClient(ctx, clientPrefixCh), + ), + ), + metadata.NewServer(), + vl3.NewServer(ctx, serverPrefixCh), + ) + + require.Eventually(t, func() bool { return len(serverPrefixCh) == 0 && len(clientPrefixCh) == 0 }, time.Second, time.Millisecond*100) + + resp, err := server.Request(ctx, &networkservice.NetworkServiceRequest{Connection: &networkservice.Connection{Id: t.Name()}}) + + require.NoError(t, err) + + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.1.0/24", resp.GetContext().GetIpContext().GetDstRoutes()[1].GetPrefix()) + + clientPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.5.0/24"} + require.Eventually(t, func() bool { return len(serverPrefixCh) == 0 && len(clientPrefixCh) == 0 }, time.Second, time.Millisecond*100) + + // refresh + for i := 0; i < 10; i++ { + resp, err = server.Request(ctx, &networkservice.NetworkServiceRequest{Connection: resp}) + + require.NoError(t, err) + + require.Equal(t, "10.0.5.0/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.5.0/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.5.0/24", resp.GetContext().GetIpContext().GetDstRoutes()[1].GetPrefix()) + } +} + +func Test_VL3NSE_ConnectsToVl3NSE_Close(t *testing.T) { + t.Cleanup(func() { + goleak.VerifyNone(t) + }) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + var serverPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(serverPrefixCh) + + serverPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + var clientPrefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(clientPrefixCh) + + clientPrefixCh <- &ipam.PrefixResponse{Prefix: "10.0.1.0/24"} + + var server = next.NewNetworkServiceServer( + adapters.NewClientToServer( + next.NewNetworkServiceClient( + begin.NewClient(), + metadata.NewClient(), + vl3.NewClient(ctx, clientPrefixCh), + ), + ), + metadata.NewServer(), + vl3.NewServer(ctx, serverPrefixCh), + ) + + require.Eventually(t, func() bool { return len(serverPrefixCh) == 0 && len(clientPrefixCh) == 0 }, time.Second, time.Millisecond*100) + + resp, err := server.Request(ctx, &networkservice.NetworkServiceRequest{Connection: &networkservice.Connection{Id: uuid.New().String()}}) + + require.NoError(t, err) + + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.1.0/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.1.0/24", resp.GetContext().GetIpContext().GetDstRoutes()[1].GetPrefix()) + + _, err = server.Close(ctx, resp) + + require.NoError(t, err) +} diff --git a/pkg/networkservice/connectioncontext/ipcontext/vl3/ipam.go b/pkg/networkservice/connectioncontext/ipcontext/vl3/ipam.go new file mode 100644 index 000000000..85f6823a3 --- /dev/null +++ b/pkg/networkservice/connectioncontext/ipcontext/vl3/ipam.go @@ -0,0 +1,143 @@ +// 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 vl3 + +import ( + "context" + "net" + "sync" + + "github.com/networkservicemesh/sdk/pkg/tools/ippool" + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +type vl3IPAM struct { + sync.Mutex + self net.IPNet + ipPool *ippool.IPPool + excludedPrefixes map[string]struct{} + clientMask uint8 +} + +func (p *vl3IPAM) isInitialized() bool { + p.Lock() + defer p.Unlock() + + return p.ipPool != nil +} + +func (p *vl3IPAM) selfAddress() *net.IPNet { + p.Lock() + defer p.Unlock() + return &net.IPNet{ + IP: p.self.IP, + Mask: net.CIDRMask( + int(p.clientMask), + int(p.clientMask), + ), + } +} + +func (p *vl3IPAM) selfPrefix() *net.IPNet { + p.Lock() + defer p.Unlock() + r := p.self + return &r +} +func (p *vl3IPAM) globalIPNet() *net.IPNet { + p.Lock() + defer p.Unlock() + return &net.IPNet{ + IP: p.self.IP, + Mask: net.CIDRMask( + int(p.clientMask)/2, + int(p.clientMask), + ), + } +} + +func (p *vl3IPAM) allocate() (*net.IPNet, error) { + p.Lock() + defer p.Unlock() + + ip, err := p.ipPool.Pull() + if err != nil { + return nil, err + } + + r := &net.IPNet{ + IP: ip, + Mask: net.CIDRMask( + int(p.clientMask), + int(p.clientMask), + ), + } + + p.excludedPrefixes[r.String()] = struct{}{} + return r, nil +} + +func (p *vl3IPAM) freeIfAllocated(ipNet string) { + p.Lock() + defer p.Unlock() + + if _, ok := p.excludedPrefixes[ipNet]; ok { + delete(p.excludedPrefixes, ipNet) + p.ipPool.AddNetString(ipNet) + } +} + +func (p *vl3IPAM) isExcluded(ipNet string) bool { + p.Lock() + defer p.Unlock() + + _, r := p.excludedPrefixes[ipNet] + return r +} + +func (p *vl3IPAM) reset(ctx context.Context, prefix string, excludePrefies []string) { + p.Lock() + defer p.Unlock() + + _, ipNet, err := net.ParseCIDR(prefix) + if err != nil { + log.FromContext(ctx).Error(err.Error()) + return + } + + p.self = *ipNet + p.ipPool = ippool.NewWithNet(ipNet) + p.excludedPrefixes = make(map[string]struct{}) + p.clientMask = net.IPv6len * 8 + if len(p.self.IP) == net.IPv4len { + p.clientMask = net.IPv4len * 8 + } + selfAddress := &net.IPNet{ + IP: p.self.IP, + Mask: net.CIDRMask( + int(p.clientMask), + int(p.clientMask), + ), + } + p.excludedPrefixes[selfAddress.String()] = struct{}{} + p.ipPool.Exclude(selfAddress) + + for _, excludePrefix := range excludePrefies { + p.ipPool.ExcludeString(excludePrefix) + p.excludedPrefixes[excludePrefix] = struct{}{} + } +} diff --git a/pkg/networkservice/connectioncontext/ipcontext/vl3/metdata.go b/pkg/networkservice/connectioncontext/ipcontext/vl3/metdata.go new file mode 100644 index 000000000..c227ab8db --- /dev/null +++ b/pkg/networkservice/connectioncontext/ipcontext/vl3/metdata.go @@ -0,0 +1,53 @@ +// 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 vl3 + +import ( + "context" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +type addressKey struct{} + +func loadAddress(ctx context.Context) (string, bool) { + v, ok := metadata.Map(ctx, false).Load(addressKey{}) + if ok { + return v.(string), true + } + + return "", false +} + +func storeAddress(ctx context.Context, address string) { + metadata.Map(ctx, false).Store(addressKey{}, address) +} + +type cancelKey struct{} + +func storeCancel(ctx context.Context, cancel context.CancelFunc) { + metadata.Map(ctx, true).Store(cancelKey{}, cancel) +} + +func loadAndDeleteCancel(ctx context.Context) (value context.CancelFunc, ok bool) { + rawValue, ok := metadata.Map(ctx, true).LoadAndDelete(cancelKey{}) + if !ok { + return + } + value, ok = rawValue.(context.CancelFunc) + return value, ok +} diff --git a/pkg/networkservice/connectioncontext/ipcontext/vl3/server.go b/pkg/networkservice/connectioncontext/ipcontext/vl3/server.go new file mode 100644 index 000000000..92b9fd8b4 --- /dev/null +++ b/pkg/networkservice/connectioncontext/ipcontext/vl3/server.go @@ -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 vl3 + +import ( + "context" + "errors" + + "github.com/golang/protobuf/ptypes/empty" + "github.com/networkservicemesh/api/pkg/api/ipam" + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" +) + +type vl3Server struct { + pool vl3IPAM +} + +// NewServer - returns a new vL3 server instance that manages connection.context.ipcontext for vL3 scenario. +// Produces refresh on prefix update. +// Requires begin and metdata chain elements. +func NewServer(ctx context.Context, prefixCh <-chan *ipam.PrefixResponse) networkservice.NetworkServiceServer { + var result = new(vl3Server) + + go func() { + for resp := range prefixCh { + result.pool.reset(ctx, resp.GetPrefix(), resp.GetExcludePrefixes()) + } + }() + + return result +} + +func (v *vl3Server) Request(ctx context.Context, request *networkservice.NetworkServiceRequest) (*networkservice.Connection, error) { + if !v.pool.isInitialized() { + return nil, errors.New("prefix pool is initializing") + } + if request.Connection == nil { + request.Connection = new(networkservice.Connection) + } + var conn = request.GetConnection() + if conn.GetContext() == nil { + conn.Context = new(networkservice.ConnectionContext) + } + if conn.GetContext().GetIpContext() == nil { + conn.GetContext().IpContext = new(networkservice.IPContext) + } + + var ipContext = &networkservice.IPContext{ + SrcIpAddrs: request.GetConnection().Context.GetIpContext().GetSrcIpAddrs(), + DstRoutes: request.GetConnection().Context.GetIpContext().GetDstRoutes(), + ExcludedPrefixes: request.GetConnection().Context.GetIpContext().GetExcludedPrefixes(), + } + + shouldAllocate := len(ipContext.SrcIpAddrs) == 0 + + if prevAddress, ok := loadAddress(ctx); ok && !shouldAllocate { + shouldAllocate = !v.pool.isExcluded(prevAddress) + } + + if shouldAllocate { + srcNet, err := v.pool.allocate() + if err != nil { + return nil, err + } + ipContext.DstRoutes = nil + ipContext.SrcIpAddrs = append([]string(nil), srcNet.String()) + storeAddress(ctx, srcNet.String()) + } + + addRoute(&ipContext.SrcRoutes, v.pool.selfAddress().String()) + addRoute(&ipContext.SrcRoutes, v.pool.selfPrefix().String()) + for _, srcAddr := range ipContext.SrcIpAddrs { + addRoute(&ipContext.DstRoutes, srcAddr) + } + addAddr(&ipContext.DstIpAddrs, v.pool.selfAddress().String()) + + conn.GetContext().IpContext = ipContext + + resp, err := next.Server(ctx).Request(ctx, request) + if err == nil { + addRoute(&resp.GetContext().GetIpContext().SrcRoutes, v.pool.globalIPNet().String()) + } + return resp, err +} + +func (v *vl3Server) Close(ctx context.Context, conn *networkservice.Connection) (*empty.Empty, error) { + for _, srcAddr := range conn.GetContext().GetIpContext().GetSrcIpAddrs() { + v.pool.freeIfAllocated(srcAddr) + } + return next.Server(ctx).Close(ctx, conn) +} + +func addRoute(routes *[]*networkservice.Route, prefix string) { + for _, route := range *routes { + if route.Prefix == prefix { + return + } + } + *routes = append(*routes, &networkservice.Route{ + Prefix: prefix, + }) +} + +func addAddr(addrs *[]string, addr string) { + for _, a := range *addrs { + if a == addr { + return + } + } + *addrs = append(*addrs, addr) +} diff --git a/pkg/networkservice/connectioncontext/ipcontext/vl3/server_test.go b/pkg/networkservice/connectioncontext/ipcontext/vl3/server_test.go new file mode 100644 index 000000000..d0f17119c --- /dev/null +++ b/pkg/networkservice/connectioncontext/ipcontext/vl3/server_test.go @@ -0,0 +1,175 @@ +// 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 vl3_test + +import ( + "context" + "testing" + "time" + + "github.com/networkservicemesh/api/pkg/api/ipam" + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/connectioncontext/ipcontext/vl3" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/next" + + "github.com/stretchr/testify/require" + "go.uber.org/goleak" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/metadata" +) + +func Test_NSC_ConnectsToVl3NSE(t *testing.T) { + t.Cleanup(func() { + goleak.VerifyNone(t) + }) + + var prefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(prefixCh) + + prefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + var server = next.NewNetworkServiceServer( + metadata.NewServer(), + vl3.NewServer(context.Background(), prefixCh), + ) + + require.Eventually(t, func() bool { return len(prefixCh) == 0 }, time.Second, time.Millisecond*100) + + resp, err := server.Request(context.Background(), new(networkservice.NetworkServiceRequest)) + + require.NoError(t, err) + + require.Equal(t, "10.0.0.1/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.0.1/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + + for i := 0; i < 10; i++ { + resp, err = server.Request(context.Background(), &networkservice.NetworkServiceRequest{Connection: resp}) + + require.NoError(t, err) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "10.0.0.1/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + + require.Equal(t, "10.0.0.1/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + } +} + +func Test_NSC_ConnectsToVl3NSE_PrefixHasChanged(t *testing.T) { + t.Cleanup(func() { + goleak.VerifyNone(t) + }) + + var prefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(prefixCh) + + prefixCh <- &ipam.PrefixResponse{Prefix: "12.0.0.1/24"} + + var server = next.NewNetworkServiceServer( + metadata.NewServer(), + vl3.NewServer(context.Background(), prefixCh), + ) + + require.Eventually(t, func() bool { return len(prefixCh) == 0 }, time.Second, time.Millisecond*120) + + resp, err := server.Request(context.Background(), new(networkservice.NetworkServiceRequest)) + + require.NoError(t, err) + + require.Equal(t, "12.0.0.1/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "12.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "12.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "12.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "12.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "12.0.0.1/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + + prefixCh <- &ipam.PrefixResponse{Prefix: "11.0.0.1/24"} + require.Eventually(t, func() bool { return len(prefixCh) == 0 }, time.Second, time.Millisecond*100) + + // refresh + for i := 0; i < 10; i++ { + resp, err = server.Request(context.Background(), &networkservice.NetworkServiceRequest{Connection: resp}) + + require.NoError(t, err) + + require.Equal(t, "11.0.0.1/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0]) + require.Equal(t, "11.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0]) + + require.Equal(t, "11.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix()) + require.Equal(t, "11.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix()) + require.Equal(t, "11.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix()) + require.Equal(t, "11.0.0.1/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix()) + } +} + +func Test_NSC_ConnectsToVl3NSE_Close(t *testing.T) { + t.Cleanup(func() { + goleak.VerifyNone(t) + }) + + var prefixCh = make(chan *ipam.PrefixResponse, 1) + defer close(prefixCh) + + prefixCh <- &ipam.PrefixResponse{Prefix: "10.0.0.1/24"} + + var server = next.NewNetworkServiceServer( + metadata.NewServer(), + vl3.NewServer(context.Background(), prefixCh), + ) + + require.Eventually(t, func() bool { return len(prefixCh) == 0 }, time.Second, time.Millisecond*100) + + for i := 0; i < 10; i++ { + resp, err := server.Request(context.Background(), new(networkservice.NetworkServiceRequest)) + + require.NoError(t, err) + + require.Equal(t, "10.0.0.1/32", resp.GetContext().GetIpContext().GetSrcIpAddrs()[0], i) + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetDstIpAddrs()[0], i) + + require.Equal(t, "10.0.0.0/32", resp.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix(), i) + require.Equal(t, "10.0.0.0/24", resp.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix(), i) + require.Equal(t, "10.0.0.0/16", resp.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix(), i) + require.Equal(t, "10.0.0.1/32", resp.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix(), i) + + resp1, err1 := server.Request(context.Background(), new(networkservice.NetworkServiceRequest)) + + require.NoError(t, err1) + + require.Equal(t, "10.0.0.2/32", resp1.GetContext().GetIpContext().GetSrcIpAddrs()[0], i) + require.Equal(t, "10.0.0.0/32", resp1.GetContext().GetIpContext().GetDstIpAddrs()[0], i) + + require.Equal(t, "10.0.0.0/32", resp1.GetContext().GetIpContext().GetSrcRoutes()[0].GetPrefix(), i) + require.Equal(t, "10.0.0.0/24", resp1.GetContext().GetIpContext().GetSrcRoutes()[1].GetPrefix(), i) + require.Equal(t, "10.0.0.0/16", resp1.GetContext().GetIpContext().GetSrcRoutes()[2].GetPrefix(), i) + require.Equal(t, "10.0.0.2/32", resp1.GetContext().GetIpContext().GetDstRoutes()[0].GetPrefix(), i) + + _, err = server.Close(context.Background(), resp1) + require.NoError(t, err, i) + _, err = server.Close(context.Background(), resp) + require.NoError(t, err, i) + } +} diff --git a/pkg/registry/common/dial/nse_client.go b/pkg/registry/common/dial/nse_client.go index c2adfc489..28e38f7e8 100644 --- a/pkg/registry/common/dial/nse_client.go +++ b/pkg/registry/common/dial/nse_client.go @@ -162,7 +162,9 @@ func (c *dialNSEClient) Find(ctx context.Context, in *registry.NetworkServiceEnd // NewNetworkServiceEndpointRegistryClient - returns a new null client that does nothing but call next.NetworkServiceEndpointRegistryClient(ctx). func NewNetworkServiceEndpointRegistryClient(chainCtx context.Context, opts ...Option) registry.NetworkServiceEndpointRegistryClient { - o := &option{} + o := &option{ + dialTimeout: time.Millisecond * 100, + } for _, opt := range opts { opt(o) } diff --git a/pkg/registry/common/recvfd/client_nolinux.go b/pkg/registry/common/recvfd/client_nolinux.go index fd31e21d8..7b374f1b5 100644 --- a/pkg/registry/common/recvfd/client_nolinux.go +++ b/pkg/registry/common/recvfd/client_nolinux.go @@ -1,6 +1,6 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,6 +16,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package recvfd diff --git a/pkg/registry/common/recvfd/server_notlinux.go b/pkg/registry/common/recvfd/server_notlinux.go index d4b5aff83..64f43b95f 100644 --- a/pkg/registry/common/recvfd/server_notlinux.go +++ b/pkg/registry/common/recvfd/server_notlinux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package recvfd diff --git a/pkg/registry/common/retry/nse_client.go b/pkg/registry/common/retry/nse_client.go index 20e331e8c..9a458feb7 100644 --- a/pkg/registry/common/retry/nse_client.go +++ b/pkg/registry/common/retry/nse_client.go @@ -102,7 +102,7 @@ func (r *retryNSEClient) Find(ctx context.Context, query *registry.NetworkServic } if r.chainCtx.Err() != nil { - return nil, ctx.Err() + return nil, r.chainCtx.Err() } return nil, ctx.Err() diff --git a/pkg/registry/common/sendfd/client_nolinux.go b/pkg/registry/common/sendfd/client_nolinux.go index 49eddd696..5cc07e8c0 100644 --- a/pkg/registry/common/sendfd/client_nolinux.go +++ b/pkg/registry/common/sendfd/client_nolinux.go @@ -1,6 +1,6 @@ -// Copyright (c) 2021 Cisco and/or its affiliates. +// Copyright (c) 2021-2022 Cisco and/or its affiliates. // -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -16,6 +16,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux // Package sendfd provides a registry.NetworkServiceEndpointRegistryClient chain element to convert any unix file socket diff --git a/pkg/registry/common/sendfd/server_nolinux.go b/pkg/registry/common/sendfd/server_nolinux.go index 621d68007..03c1e3e86 100644 --- a/pkg/registry/common/sendfd/server_nolinux.go +++ b/pkg/registry/common/sendfd/server_nolinux.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !linux // +build !linux package sendfd diff --git a/pkg/registry/core/trace/ns_registry.go b/pkg/registry/core/trace/ns_registry.go index dc96e94bb..29663de2a 100644 --- a/pkg/registry/core/trace/ns_registry.go +++ b/pkg/registry/core/trace/ns_registry.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -50,6 +50,9 @@ func (t *traceNetworkServiceRegistryFindClient) Recv() (*registry.NetworkService if err == io.EOF { return nil, err } + if err == context.Canceled { + return nil, err + } return nil, logError(ctx, err, operation) } logObjectTrace(ctx, "recv-response", rv.NetworkService) diff --git a/pkg/registry/core/trace/nse_registry.go b/pkg/registry/core/trace/nse_registry.go index 274a67d1c..5f5ab71a9 100644 --- a/pkg/registry/core/trace/nse_registry.go +++ b/pkg/registry/core/trace/nse_registry.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -50,6 +50,9 @@ func (t *traceNetworkServiceEndpointRegistryFindClient) Recv() (*registry.Networ if err == io.EOF { return nil, err } + if err == context.Canceled { + return nil, err + } return nil, logError(ctx, err, operation) } logObjectTrace(ctx, "recv-response", rv) diff --git a/pkg/tools/awarenessgroups/config_decoder.go b/pkg/tools/awarenessgroups/config_decoder.go new file mode 100644 index 000000000..98a3cab6d --- /dev/null +++ b/pkg/tools/awarenessgroups/config_decoder.go @@ -0,0 +1,101 @@ +// 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 awarenessgroups provides awareness groups specific tools +package awarenessgroups + +import ( + "net/url" + "strings" + + "github.com/pkg/errors" +) + +// Decoder allows to parse [][]*url.URL from string. Can be used for env configuration. +// See at https://github.com/kelseyhightower/envconfig#custom-decoders +type Decoder [][]*url.URL + +// Decode parses values from passed string. +func (d *Decoder) Decode(value string) error { + value = strings.ReplaceAll(value, " ", "") + if value == "" { + return nil + } + if err := validateParentheses(value); err != nil { + return err + } + + lists := strings.Split(value, "],[") + awarenessGroups := make([][]*url.URL, len(lists)) + for i, list := range lists { + list = strings.Trim(list, "[]") + groupItems := strings.Split(list, ",") + for _, item := range groupItems { + if item == "" { + return errors.New("empty nsurl") + } + nsurl, err := url.Parse(item) + if err != nil { + return err + } + awarenessGroups[i] = append(awarenessGroups[i], nsurl) + } + } + + *d = Decoder(awarenessGroups) + return nil +} + +// TODO: write validation fuction for awarenessGroups values +func validateParentheses(value string) error { + i := 0 + length := len(value) + if length < 2 { + return errors.New("value is too short") + } + + parenthesesCounter := 0 + for ; i < length; i++ { + if value[i] == '[' { + if parenthesesCounter == 1 { + return errors.Errorf("unexpected character: %c", value[i]) + } + parenthesesCounter++ + } else if value[i] == ']' { + if parenthesesCounter == 0 { + return errors.Errorf("unexpected character: %c", value[i]) + } + if i+1 == length { + return nil + } + if i+2 == length { + return errors.New("unexpected end of value") + } + if value[i+1] != ',' { + return errors.Errorf("unexpected character: %c", value[i+1]) + } + if value[i+2] != '[' { + return errors.Errorf("unexpected character: %c", value[i+2]) + } + parenthesesCounter-- + } + } + + if parenthesesCounter != 0 { + return errors.New("parenteses are not balanced") + } + return nil +} diff --git a/pkg/tools/awarenessgroups/config_decoder_test.go b/pkg/tools/awarenessgroups/config_decoder_test.go new file mode 100644 index 000000000..30586316d --- /dev/null +++ b/pkg/tools/awarenessgroups/config_decoder_test.go @@ -0,0 +1,75 @@ +// 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 awarenessgroups + +import ( + "fmt" + "net/url" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_AwarenessGroupsDecoder_EmptyInput(t *testing.T) { + var expected [][]*url.URL + var decoder Decoder + err := decoder.Decode(``) + require.NoError(t, err) + require.Equal(t, expected, [][]*url.URL(decoder)) +} + +func Test_AwarenessGroupsDecoder_CorrectInput(t *testing.T) { + url1, err := url.Parse("kernel://ns-1/nsm-1?color=red") + require.NoError(t, err) + url2, err := url.Parse("kernel://ns-2/nsm-2?color=blue") + require.NoError(t, err) + url3, err := url.Parse("kernel://ns-3/nsm-3?color=yellow") + require.NoError(t, err) + url4, err := url.Parse("kernel://ns-4/nsm-4?color=white") + require.NoError(t, err) + + var expected = [][]*url.URL{ + {url1, url2}, + {url3}, + {url4}, + } + var decoder Decoder + err = decoder.Decode(fmt.Sprintf("[%v, %v], [%v], [%v]", url1, url2, url3, url4)) + require.NoError(t, err) + require.Equal(t, expected, [][]*url.URL(decoder)) +} + +func Test_AwarenessGroupsDecoder_WrongInput(t *testing.T) { + var decoder Decoder + err := decoder.Decode("[a, b], [[c],,[d]") + require.Error(t, err) + + err = decoder.Decode("[a, b") + require.Error(t, err) + + err = decoder.Decode("[a, b],") + require.Error(t, err) + + err = decoder.Decode("[a, b][c]") + require.Error(t, err) + + err = decoder.Decode("[a, b],[]") + require.Error(t, err) + + err = decoder.Decode("[a,, b]") + require.Error(t, err) +} diff --git a/pkg/tools/fs/inode.go b/pkg/tools/fs/inode.go deleted file mode 100644 index 30fdabaef..000000000 --- a/pkg/tools/fs/inode.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2020-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. - -//go:build linux || darwin || freebsd || netbsd || openbsd -// +build linux darwin freebsd netbsd openbsd - -// Package fs provides common filesystem functions and utilities -package fs - -import ( - "os" - "syscall" - - "github.com/pkg/errors" -) - -// GetInode returns Inode for file -func GetInode(file string) (uintptr, error) { - fileInfo, err := os.Stat(file) - if err != nil { - return 0, errors.Wrap(err, "error stat file") - } - stat, ok := fileInfo.Sys().(*syscall.Stat_t) - if !ok { - return 0, errors.New("not a stat_t") - } - return uintptr(stat.Ino), nil -} diff --git a/pkg/tools/fs/utils.go b/pkg/tools/fs/utils.go index 42552ff40..9e21a87a0 100644 --- a/pkg/tools/fs/utils.go +++ b/pkg/tools/fs/utils.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2021 Doc.ai and/or its affiliates. +// Copyright (c) 2020-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package fs provides common filesystem functions and utilities package fs import ( diff --git a/pkg/tools/ippool/ippool.go b/pkg/tools/ippool/ippool.go index 8bc32d1a0..b1b368ed5 100644 --- a/pkg/tools/ippool/ippool.go +++ b/pkg/tools/ippool/ippool.go @@ -1,4 +1,6 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. +// +// Copyright (c) 2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -145,6 +147,41 @@ func (tree *IPPool) AddNetString(ipNetString string) { tree.AddNet(ipNet) } +// ContainsNetString parses ipNetRaw string and checks that pool contains whole ipNet +func (tree *IPPool) ContainsNetString(ipNetRaw string) bool { + _, ipNet, err := net.ParseCIDR(ipNetRaw) + if err != nil { + return false + } + + return tree.ContainsNet(ipNet) +} + +// ContainsNet checks that pool contains whole ipNet +func (tree *IPPool) ContainsNet(ipNet *net.IPNet) bool { + if ipNet == nil { + return false + } + + var node = tree.root + var ipRange = ipRangeFromIPNet(ipNet) + + for node != nil { + compare := node.Value.CompareRange(ipRange) + switch { + case compare < 0: + node = node.Left + case compare > 0: + node = node.Right + default: + lRange, rRange := ipRange.Sub(node.Value) + return lRange == nil && rRange == nil + } + } + + return false +} + // Contains - check the pool contains ip address func (tree *IPPool) Contains(ip net.IP) bool { if ip == nil { diff --git a/pkg/tools/ippool/ippool_test.go b/pkg/tools/ippool/ippool_test.go index 50b55d4fa..37a6c8375 100644 --- a/pkg/tools/ippool/ippool_test.go +++ b/pkg/tools/ippool/ippool_test.go @@ -1,4 +1,6 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. +// +// Copyright (c) 2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -17,6 +19,7 @@ package ippool import ( + "fmt" "math/rand" "net" "runtime" @@ -62,6 +65,67 @@ func TestIPPoolTool_AddRange(t *testing.T) { require.Equal(t, ipPool.size, uint64(2)) } +func TestGlobalCIDR(t *testing.T) { + ipPool := NewWithNetString("192.168.0.0/16") + require.True(t, ipPool.ContainsNetString("192.168.0.1/32")) + require.False(t, ipPool.ContainsNetString("193.169.0.1/32")) +} + +func TestIsItWorkCorrect(t *testing.T) { + ipPool := NewWithNetString("192.168.0.0/16") + ipPool.ExcludeString("192.168.0.0/30") + p, err := ipPool.Pull() + + require.NoError(t, err) + + prefix := p.String() + "/24" + fmt.Println(prefix) + + pool2 := NewWithNetString(prefix) + pool2.ExcludeString("192.168.0.0/30") + require.False(t, pool2.ContainsString("192.168.0.1")) +} + +func TestIPPool_ExcludeRange(t *testing.T) { + ipPool := NewWithNetString("192.168.0.0/16") + + for i := 0; i < 3; i++ { + p, err := ipPool.Pull() + + require.NoError(t, err) + + prefix := p.String() + "/24" + + ipPool.ExcludeString(prefix) + + require.Equal(t, fmt.Sprintf("192.168.%v.0/24", i), prefix) + } +} + +func Test_IPPoolContains(t *testing.T) { + ipPool := NewWithNetString("10.10.0.0/16") + + for i := 1; i <= 255; i++ { + if i == 10 { + continue + } + for j := 1; j <= 255; j++ { + if j == 10 { + continue + } + require.False(t, ipPool.ContainsNetString(fmt.Sprintf("%v.%v.0.0/24", i, j))) + } + } + for i := 16; i < 32; i++ { + ipNet := "10.10.0.0/" + fmt.Sprint(i) + require.True(t, ipPool.ContainsNetString(ipNet), ipNet) + } + for i := 15; i > 0; i-- { + ipNet := "10.10.0.0/" + fmt.Sprint(i) + require.False(t, ipPool.ContainsNetString(ipNet), ipNet) + } +} + func TestIPPoolTool_Contains(t *testing.T) { ipPool := NewWithNetString("192.168.0.0/16") diff --git a/pkg/tools/ippool/tools.go b/pkg/tools/ippool/tools.go index 3731d88b2..e1821391d 100644 --- a/pkg/tools/ippool/tools.go +++ b/pkg/tools/ippool/tools.go @@ -1,4 +1,6 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. +// +// Copyright (c) 2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/pkg/tools/ippool/types.go b/pkg/tools/ippool/types.go index d019b7943..1bad674fa 100644 --- a/pkg/tools/ippool/types.go +++ b/pkg/tools/ippool/types.go @@ -1,4 +1,6 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. +// +// Copyright (c) 2022 Cisco and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -118,7 +120,7 @@ func (b *ipRange) CompareRange(ipR *ipRange) int { } if b.Compare(ipR.start) > 0 && b.Compare(ipR.end) > 0 { if !ipR.end.IsFirst() && b.Compare(ipR.start.Prev()) == 0 { - return -1 + return 1 } return 2 } diff --git a/pkg/tools/sandbox/grpc_utils_windows.go b/pkg/tools/sandbox/grpc_utils_windows.go index 35b4c02fa..50ffd9c32 100644 --- a/pkg/tools/sandbox/grpc_utils_windows.go +++ b/pkg/tools/sandbox/grpc_utils_windows.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build windows // +build windows package sandbox diff --git a/pkg/tools/sandbox/node.go b/pkg/tools/sandbox/node.go index cb4455972..8b56050f8 100644 --- a/pkg/tools/sandbox/node.go +++ b/pkg/tools/sandbox/node.go @@ -234,15 +234,19 @@ func (n *Node) registerEndpoint( func (n *Node) NewClient( ctx context.Context, generatorFunc token.GeneratorFunc, - additionalFunctionality ...networkservice.NetworkServiceClient, + additionalOpts ...client.Option, ) networkservice.NetworkServiceClient { - return retry.NewClient(client.NewClient( - ctx, + opts := []client.Option{ client.WithClientURL(CloneURL(n.NSMgr.URL)), client.WithDialOptions(DialOptions(WithTokenGenerator(generatorFunc))...), client.WithAuthorizeClient(authorize.NewClient(authorize.Any())), client.WithHealClient(heal.NewClient(ctx)), - client.WithAdditionalFunctionality(additionalFunctionality...), client.WithDialTimeout(DialTimeout), + } + + opts = append(opts, additionalOpts...) + return retry.NewClient(client.NewClient( + ctx, + opts..., )) } diff --git a/staticcheck.conf b/staticcheck.conf index 6b78d4716..b8e9fc214 100644 --- a/staticcheck.conf +++ b/staticcheck.conf @@ -1,6 +1,6 @@ checks = ["all", "-ST1000", "-ST1016"] initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", - "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", + "EOF", "GRPC", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "NS", "NSM", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI",