diff --git a/.golangci.yml b/.golangci.yml index 511e771..7c24988 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -110,7 +110,6 @@ linters-settings: - regexpPattern - singleCaseSwitch - sloppyLen - - sloppyReassign - stringXbytes - switchTrue - typeAssertChain diff --git a/go.mod b/go.mod index 1448ba7..69e5db6 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 - go.uber.org/atomic v1.7.0 go.uber.org/goleak v1.1.12 golang.org/x/tools v0.1.10 // indirect google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 // indirect diff --git a/pkg/networkservice/common/createpod/option.go b/pkg/networkservice/common/createpod/option.go index f527564..5fb7160 100644 --- a/pkg/networkservice/common/createpod/option.go +++ b/pkg/networkservice/common/createpod/option.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 // @@ -25,11 +25,3 @@ func WithNamespace(namespace string) Option { t.namespace = namespace } } - -// WithNameGenerator sets function to be used for pod name generation. -// Default behavior is to append a random uuid to the template name. -func WithNameGenerator(nameGenerator func(templateName, nodeName string) string) Option { - return func(t *createPodServer) { - t.nameGenerator = nameGenerator - } -} diff --git a/pkg/networkservice/common/createpod/server.go b/pkg/networkservice/common/createpod/server.go index bb7d0a8..7150d59 100644 --- a/pkg/networkservice/common/createpod/server.go +++ b/pkg/networkservice/common/createpod/server.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 // @@ -18,14 +18,19 @@ package createpod import ( + "bytes" "context" "sync" + "text/template" "github.com/golang/protobuf/ptypes/empty" "github.com/google/uuid" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes" @@ -39,12 +44,12 @@ const ( ) type createPodServer struct { - ctx context.Context - client kubernetes.Interface - podTemplate *corev1.Pod - namespace string - nodeMap nodeInfoMap - nameGenerator func(templateName, nodeName string) string + ctx context.Context + client kubernetes.Interface + podTemplate string + namespace string + nodeMap nodeInfoMap + deserializer runtime.Decoder } type nodeInfo struct { @@ -56,13 +61,17 @@ type nodeInfo struct { // // Pods are created on the node with a name specified by key "NodeNameKey" in request labels // (this label is expected to be filled by clientinfo client). -func NewServer(ctx context.Context, client kubernetes.Interface, podTemplate *corev1.Pod, options ...Option) networkservice.NetworkServiceServer { +func NewServer(ctx context.Context, client kubernetes.Interface, podTemplate string, options ...Option) networkservice.NetworkServiceServer { + scheme := runtime.NewScheme() + codecFactory := serializer.NewCodecFactory(scheme) + deserializer := codecFactory.UniversalDeserializer() + s := &createPodServer{ - ctx: ctx, - podTemplate: podTemplate.DeepCopy(), - client: client, - namespace: "default", - nameGenerator: func(templateName, nodeName string) string { return templateName + "-" + uuid.New().String() }, + ctx: ctx, + podTemplate: podTemplate, + client: client, + namespace: "default", + deserializer: deserializer, } for _, opt := range options { @@ -119,11 +128,11 @@ func (s *createPodServer) Request(ctx context.Context, request *networkservice.N return nil, errors.New("cannot provide required networkservice: local endpoint already exists") } - ni.name = s.nameGenerator(s.podTemplate.ObjectMeta.Name, nodeName) - err := s.createPod(ctx, nodeName, ni.name) + name, err := s.createPod(ctx, nodeName, request.GetConnection()) if err != nil { return nil, errors.WithStack(err) } + ni.name = name return nil, errors.Errorf("cannot provide required networkservice: local endpoint created as %v", ni.name) } @@ -131,11 +140,35 @@ func (s *createPodServer) Close(ctx context.Context, conn *networkservice.Connec return next.Server(ctx).Close(ctx, conn) } -func (s *createPodServer) createPod(ctx context.Context, nodeName, podName string) error { - podTemplate := s.podTemplate.DeepCopy() - podTemplate.ObjectMeta.Name = podName - podTemplate.Spec.NodeName = nodeName +func (s *createPodServer) createPod(ctx context.Context, nodeName string, conn *networkservice.Connection) (string, error) { + var t, err = template.New("createPod").Funcs(template.FuncMap{ + "uuid": func() string { + return uuid.New().String() + }, + }).Parse(s.podTemplate) + + if err != nil { + return "", err + } + var buffer bytes.Buffer + if err = t.Execute(&buffer, conn); err != nil { + return "", err + } + var pod corev1.Pod + + _, _, err = s.deserializer.Decode(buffer.Bytes(), nil, &pod) + if err != nil { + return "", err + } + + if pod.Spec.NodeName == "" { + pod.Spec.NodeName = nodeName + } + + resp, err := s.client.CoreV1().Pods(s.namespace).Create(ctx, &pod, metav1.CreateOptions{}) + if err != nil { + return "", err + } - _, err := s.client.CoreV1().Pods(s.namespace).Create(ctx, podTemplate, metav1.CreateOptions{}) - return err + return resp.GetObjectMeta().GetName(), nil } diff --git a/pkg/networkservice/common/createpod/server_test.go b/pkg/networkservice/common/createpod/server_test.go index c520712..6ac2bc1 100644 --- a/pkg/networkservice/common/createpod/server_test.go +++ b/pkg/networkservice/common/createpod/server_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) 2021-2022 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -19,13 +21,10 @@ package createpod_test import ( "context" "os" - "strconv" "testing" "time" "github.com/stretchr/testify/require" - "go.uber.org/atomic" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -49,17 +48,10 @@ func TestCreatePod_RepeatedRequest(t *testing.T) { clientSet := fake.NewSimpleClientset() - podTemplate := defaultPodTemplate() - - counter := atomic.Int32{} - server := next.NewNetworkServiceServer( adapters.NewClientToServer(clientinfo.NewClient()), - createpod.NewServer(ctx, clientSet, podTemplate, + createpod.NewServer(ctx, clientSet, defaultPodTemplate, createpod.WithNamespace(testNamespace), - createpod.WithNameGenerator(func(templateName, nodeName string) string { - return templateName + strconv.Itoa(int(counter.Add(1))) - }), ), ) @@ -71,7 +63,7 @@ func TestCreatePod_RepeatedRequest(t *testing.T) { Connection: &networkservice.Connection{}, }) require.Error(t, err) - require.Equal(t, "cannot provide required networkservice: local endpoint created as PodName1", err.Error()) + require.Contains(t, err.Error(), "cannot provide required networkservice: local endpoint created as ") // second request: should fail _, err = server.Request(ctx, &networkservice.NetworkServiceRequest{ @@ -85,10 +77,6 @@ func TestCreatePod_RepeatedRequest(t *testing.T) { require.Equal(t, 1, len(podList.Items)) pod := podList.Items[0].DeepCopy() - want := podTemplate.DeepCopy() - want.Spec.NodeName = nodeName1 - require.Equal(t, pod.Spec, want.Spec) - pod.Status.Phase = "Succeeded" _, err = clientSet.CoreV1().Pods(testNamespace).UpdateStatus(ctx, pod, metav1.UpdateOptions{}) require.NoError(t, err) @@ -99,7 +87,10 @@ func TestCreatePod_RepeatedRequest(t *testing.T) { Connection: &networkservice.Connection{}, }) require.Error(t, err) - return err.Error() == "cannot provide required networkservice: local endpoint created as PodName2" + + podList, err = clientSet.CoreV1().Pods(testNamespace).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + return len(podList.Items) > 0 && podList.Items[0].GetName() != pod.GetName() }, time.Millisecond*100, time.Millisecond*10) podList, err = clientSet.CoreV1().Pods(testNamespace).List(ctx, metav1.ListOptions{}) @@ -117,17 +108,10 @@ func TestCreatePod_TwoNodes(t *testing.T) { clientSet := fake.NewSimpleClientset() - podTemplate := defaultPodTemplate() - - counter := atomic.Int32{} - server := next.NewNetworkServiceServer( adapters.NewClientToServer(clientinfo.NewClient()), - createpod.NewServer(ctx, clientSet, podTemplate, + createpod.NewServer(ctx, clientSet, defaultPodTemplate, createpod.WithNamespace(testNamespace), - createpod.WithNameGenerator(func(templateName, nodeName string) string { - return templateName + strconv.Itoa(int(counter.Add(1))) - }), ), ) @@ -135,43 +119,53 @@ func TestCreatePod_TwoNodes(t *testing.T) { require.NoError(t, err) _, err = server.Request(ctx, &networkservice.NetworkServiceRequest{ - Connection: &networkservice.Connection{}, + Connection: &networkservice.Connection{ + Labels: map[string]string{ + "a": "b", + "c": "e", + }, + }, }) require.Error(t, err) - require.Equal(t, "cannot provide required networkservice: local endpoint created as PodName1", err.Error()) + require.Contains(t, err.Error(), "cannot provide required networkservice: local endpoint created as ") err = os.Setenv("NODE_NAME", nodeName2) require.NoError(t, err) _, err = server.Request(ctx, &networkservice.NetworkServiceRequest{ - Connection: &networkservice.Connection{}, + Connection: &networkservice.Connection{ + Labels: map[string]string{ + "a": "b", + "c": "e", + }, + }, }) require.Error(t, err) - require.Equal(t, "cannot provide required networkservice: local endpoint created as PodName2", err.Error()) + require.Contains(t, err.Error(), "cannot provide required networkservice: local endpoint created as ") podList, err := clientSet.CoreV1().Pods(testNamespace).List(ctx, metav1.ListOptions{}) require.NoError(t, err) + var nodesSet = map[string]struct{}{ + nodeName1: {}, + nodeName2: {}, + } + require.Contains(t, podList.Items[0].GetLabels(), "a") + require.Contains(t, podList.Items[0].GetLabels(), "c") require.Equal(t, 2, len(podList.Items)) - require.Equal(t, nodeName1, podList.Items[0].Spec.NodeName) - require.Equal(t, nodeName2, podList.Items[1].Spec.NodeName) + require.Contains(t, nodesSet, podList.Items[0].Spec.NodeName) + delete(nodesSet, podList.Items[0].Spec.NodeName) + require.Contains(t, nodesSet, podList.Items[1].Spec.NodeName) } -func defaultPodTemplate() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "PodName", - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "my-container-1", - Image: "my-image-1", - }, - { - Name: "my-container-2", - Image: "my-image-2", - }, - }, - }, - } -} +const defaultPodTemplate = `--- +apiVersion: apps/v1 +kind: Pod +metadata: + name: nse-{{ uuid }} + labels: {{ range $key, $value := .Labels }} + "{{ $key }}": "{{ $value }}"{{ end }} +objectmeta: + containers: + - name: my-container-1 + image: my-image-1 +`