Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement initial version of cmd-map-ip-k8s #1

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ RUN dl https://github.com/spiffe/spire/releases/download/v0.11.1/spire-0.11.1-li

FROM go as build
WORKDIR /build
COPY go.mod go.sum ./
COPY ./internal/imports imports
RUN go build ./imports
COPY . .
RUN go build -o /bin/app .

Expand Down
16 changes: 15 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
module github.com/networkservicemesh/cmd-template
module github.com/networkservicemesh/cmd-map-ip-k8s

go 1.16

require (
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/edwarnicke/serialize v1.0.7
github.com/kelseyhightower/envconfig v1.4.0
github.com/networkservicemesh/sdk v0.0.0-20210531072719-eaf26022ad56
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
go.uber.org/goleak v1.1.10
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.21.1
k8s.io/apimachinery v0.21.1
k8s.io/client-go v0.21.1
)
578 changes: 578 additions & 0 deletions go.sum

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions internal/imports/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2021 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 imports is used for generating list of imports to optimize use of docker build cache
package imports

//go:generate bash -c "rm -rf imports_linux.go"
//go:generate bash -c "cd $(mktemp -d) && GO111MODULE=on go get github.com/edwarnicke/imports-gen@v1.1.0"
//go:generate bash -c "GOOS=linux ${GOPATH}/bin/imports-gen"
29 changes: 29 additions & 0 deletions internal/imports/imports_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// DO NOT EDIT - generated by github.com/edwarnicke/imports-gen
package imports

import (
_ "context"
_ "github.com/antonfisher/nested-logrus-formatter"
_ "github.com/edwarnicke/serialize"
_ "github.com/kelseyhightower/envconfig"
_ "github.com/networkservicemesh/sdk/pkg/tools/log"
_ "github.com/networkservicemesh/sdk/pkg/tools/log/logruslogger"
_ "github.com/sirupsen/logrus"
_ "github.com/stretchr/testify/require"
_ "go.uber.org/goleak"
_ "gopkg.in/yaml.v2"
_ "io/ioutil"
_ "k8s.io/api/core/v1"
_ "k8s.io/apimachinery/pkg/apis/meta/v1"
_ "k8s.io/apimachinery/pkg/watch"
_ "k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/kubernetes/fake"
_ "k8s.io/client-go/rest"
_ "os"
_ "os/signal"
_ "path/filepath"
_ "strings"
_ "syscall"
_ "testing"
_ "time"
)
119 changes: 119 additions & 0 deletions internal/mapipwriter/mapipwriter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// 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 mapipwriter provides MapIPWriter struct that can handle events related to v1.Node
package mapipwriter

import (
"context"
"io/ioutil"
"os"
"path/filepath"

"github.com/edwarnicke/serialize"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/watch"

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

// MapIPWriter writes IPs from the v1.Node into OutputPath
type MapIPWriter struct {
OutputPath string
exec serialize.Executor
internalToExternalIP map[string]string
}

func (m *MapIPWriter) writeToFile(ctx context.Context) {
_ = os.MkdirAll(filepath.Dir(m.OutputPath), os.ModePerm)

bytes, err := yaml.Marshal(m.internalToExternalIP)

if err != nil {
log.FromContext(ctx).Errorf("an error during marshaling ips map: %v, err: %v", m.OutputPath, err.Error())
return
}

err = ioutil.WriteFile(m.OutputPath, bytes, os.ModePerm)

if err != nil {
log.FromContext(ctx).Errorf("an error during marshaling ips map: %v, err: %v", m.OutputPath, err.Error())
}
}

// Start starts reading events from the passed channel in the current goroutine
func (m *MapIPWriter) Start(ctx context.Context, eventCh <-chan watch.Event) {
for {
select {
case <-ctx.Done():
return
case event, ok := <-eventCh:
if !ok {
continue
}
node, ok := event.Object.(*v1.Node)
if !ok {
continue
}
if node == nil {
continue
}

internalIP, externalIP := getAddress(node.Status.Addresses, v1.NodeInternalIP), getAddress(node.Status.Addresses, v1.NodeExternalIP)

if internalIP == "" && externalIP == "" {
continue
}

if internalIP == "" {
internalIP = externalIP
}

if externalIP == "" {
externalIP = internalIP
}

eventType := event.Type

m.exec.AsyncExec(func() {
if m.internalToExternalIP == nil {
m.internalToExternalIP = map[string]string{}
}
switch eventType {
case watch.Deleted:
delete(m.internalToExternalIP, internalIP)
default:
m.internalToExternalIP[internalIP] = externalIP
}
m.exec.AsyncExec(func() {
m.writeToFile(ctx)
})
})
}
}
}

func getAddress(addresses []v1.NodeAddress, addressType v1.NodeAddressType) string {
for i := 0; i < len(addresses); i++ {
addr := &addresses[i]
if addr.Type == addressType {
return addr.Address
}
}

return ""
}
114 changes: 114 additions & 0 deletions internal/mapipwriter/mapipwriter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// 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 mapipwriter_test

import (
"context"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"time"

"github.com/stretchr/testify/require"
"go.uber.org/goleak"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"

"github.com/networkservicemesh/cmd-map-ip-k8s/internal/mapipwriter"
)

func Test_MapWriter(t *testing.T) {
defer goleak.VerifyNone(t, goleak.IgnoreCurrent())

outputFile := filepath.Join(t.TempDir(), "output.yaml")

ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

var writer = mapipwriter.MapIPWriter{
OutputPath: outputFile,
}

cleintset := fake.NewSimpleClientset()

w, err := cleintset.CoreV1().Nodes().Watch(ctx, metav1.ListOptions{})
require.NoError(t, err)

defer w.Stop()

go writer.Start(ctx, w.ResultChan())

_, err = cleintset.CoreV1().Nodes().Create(ctx, &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-controlplane",
},
Status: v1.NodeStatus{

Addresses: []v1.NodeAddress{
{
Type: v1.NodeExternalIP,
Address: "148.142.120.1",
},
{
Type: v1.NodeInternalIP,
Address: "127.0.0.1",
},
},
},
}, metav1.CreateOptions{})
require.NoError(t, err)

_, err = cleintset.CoreV1().Nodes().Create(ctx, &v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node-worker-1",
},
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Type: v1.NodeInternalIP,
Address: "1.1.1.1",
},
},
},
}, metav1.CreateOptions{})
require.NoError(t, err)

require.Eventually(t, func() bool {
// #nosec
b, readErr := ioutil.ReadFile(outputFile)
if readErr != nil {
return false
}
s := string(b)
return strings.Contains(s, "127.0.0.1: 148.142.120.1") && strings.Contains(s, "1.1.1.1: 1.1.1.1")
}, time.Second, time.Millisecond*100)

err = cleintset.CoreV1().Nodes().Delete(ctx, "node-worker-1", metav1.DeleteOptions{})
require.NoError(t, err)

require.Eventually(t, func() bool {
// #nosec
b, readErr := ioutil.ReadFile(outputFile)
if readErr != nil {
return false
}
s := strings.TrimSpace(string(b))
return s == "127.0.0.1: 148.142.120.1"
}, time.Second, time.Millisecond*100)
}
Loading