Skip to content

Issue 30 tcp http support #31

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

Merged
merged 12 commits into from
Apr 10, 2023
9 changes: 5 additions & 4 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ stateDiagram-v2
Handler --> Translator
Translator --> Handler
Handler --> Synchronizer : "nkl-synchronizer queue"
Synchronizer --> NGINXPlusLB1
Synchronizer --> NGINXPlusLB2
Synchronizer --> NGINXPlusLB...
Synchronizer --> NGINXPlusLBn
Synchronizer --> BorderClient : "HttpBorderClient | TcpBorderClient"
BorderClient --> NGINXPlusLB1
BorderClient --> NGINXPlusLB2
BorderClient --> NGINXPlusLB...
BorderClient --> NGINXPlusLBn
```

### Settings
Expand Down
43 changes: 43 additions & 0 deletions internal/application/application_common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import (
"errors"
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
"github.com/nginxinc/kubernetes-nginx-ingress/test/mocks"
)

const (
deletedEventType = core.Deleted
createEventType = core.Created
upstreamName = "upstreamName"
server = "server"
)

func buildTerrorizingBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) {
nginxClient := mocks.NewErroringMockClient(errors.New(`something went horribly horribly wrong`))
bc, err := NewBorderClient(clientType, nginxClient)

return bc, nginxClient, err
}

func buildBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) {
nginxClient := mocks.NewMockNginxClient()
bc, err := NewBorderClient(clientType, nginxClient)

return bc, nginxClient, err
}

func buildServerUpdateEvent(eventType core.EventType, clientType string) *core.ServerUpdateEvent {
upstreamServers := core.UpstreamServers{
{
Host: server,
},
}

return core.NewServerUpdateEvent(eventType, upstreamName, clientType, upstreamServers)
}
11 changes: 11 additions & 0 deletions internal/application/application_constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

const (
ClientTypeTcp = "tcp"
ClientTypeHttp = "http"
)
37 changes: 37 additions & 0 deletions internal/application/border_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import (
"fmt"
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
"github.com/sirupsen/logrus"
)

type Interface interface {
Update(*core.ServerUpdateEvent) error
Delete(*core.ServerUpdateEvent) error
}

type BorderClient struct {
}

// NewBorderClient Returns a NullBorderClient if the type is unknown, this avoids panics due to nil pointer dereferences.
func NewBorderClient(clientType string, borderClient interface{}) (Interface, error) {
logrus.Debugf(`NewBorderClient for type: %s`, clientType)

switch clientType {
case "tcp":
return NewTcpBorderClient(borderClient)

case "http":
return NewHttpBorderClient(borderClient)

default:
borderClient, _ := NewNullBorderClient()
return borderClient, fmt.Errorf(`unknown border client type: %s`, clientType)
}
}
52 changes: 52 additions & 0 deletions internal/application/border_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import (
"github.com/nginxinc/kubernetes-nginx-ingress/test/mocks"
"testing"
)

func TestBorderClient_CreatesHttpBorderClient(t *testing.T) {
borderClient := mocks.MockNginxClient{}
client, err := NewBorderClient("http", borderClient)
if err != nil {
t.Errorf(`error creating border client: %v`, err)
}

if _, ok := client.(*HttpBorderClient); !ok {
t.Errorf(`expected client to be of type HttpBorderClient`)
}
}

func TestBorderClient_CreatesTcpBorderClient(t *testing.T) {
borderClient := mocks.MockNginxClient{}
client, err := NewBorderClient("tcp", borderClient)
if err != nil {
t.Errorf(`error creating border client: %v`, err)
}

if _, ok := client.(*TcpBorderClient); !ok {
t.Errorf(`expected client to be of type TcpBorderClient`)
}
}

func TestBorderClient_UnknownClientType(t *testing.T) {
unknownClientType := "unknown"
borderClient := mocks.MockNginxClient{}
client, err := NewBorderClient(unknownClientType, borderClient)
if err == nil {
t.Errorf(`expected error creating border client`)
}

if err.Error() != `unknown border client type: unknown` {
t.Errorf(`expected error to be 'unknown border client type: unknown', got: %v`, err)
}

if _, ok := client.(*NullBorderClient); !ok {
t.Errorf(`expected client to be of type NullBorderClient`)
}
}
19 changes: 19 additions & 0 deletions internal/application/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

/*
Package application includes support for applying updates to the Border servers.

"Border TcpServers" are the servers that are exposed to the outside world and direct traffic into the cluster.
At this time the only supported Border TcpServers are NGINX Plus servers. The BorderClient module defines
an interface that can be implemented to support other Border Server types.

- HttpBorderClient: updates NGINX Plus servers using HTTP Upstream methods on the NGINX Plus API.
- TcpBorderClient: updates NGINX Plus servers using Stream Upstream methods on the NGINX Plus API.

Selection of the appropriate client is based on the Annotations present on the NodePort Service definition.
*/

package application
63 changes: 63 additions & 0 deletions internal/application/http_border_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import (
"fmt"
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
nginxClient "github.com/nginxinc/nginx-plus-go-client/client"
)

type HttpBorderClient struct {
BorderClient
nginxClient NginxClientInterface
}

func NewHttpBorderClient(client interface{}) (Interface, error) {
ngxClient, ok := client.(NginxClientInterface)
if !ok {
return nil, fmt.Errorf(`expected a NginxClientInterface, got a %v`, client)
}

return &HttpBorderClient{
nginxClient: ngxClient,
}, nil
}

func (hbc *HttpBorderClient) Update(event *core.ServerUpdateEvent) error {
httpUpstreamServers := asNginxHttpUpstreamServers(event.UpstreamServers)
_, _, _, err := hbc.nginxClient.UpdateHTTPServers(event.UpstreamName, httpUpstreamServers)
if err != nil {
return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err)
}

return nil
}

func (hbc *HttpBorderClient) Delete(event *core.ServerUpdateEvent) error {
err := hbc.nginxClient.DeleteHTTPServer(event.UpstreamName, event.UpstreamServers[0].Host)
if err != nil {
return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err)
}

return nil
}

func asNginxHttpUpstreamServer(server *core.UpstreamServer) nginxClient.UpstreamServer {
return nginxClient.UpstreamServer{
Server: server.Host,
}
}

func asNginxHttpUpstreamServers(servers core.UpstreamServers) []nginxClient.UpstreamServer {
var upstreamServers []nginxClient.UpstreamServer

for _, server := range servers {
upstreamServers = append(upstreamServers, asNginxHttpUpstreamServer(server))
}

return upstreamServers
}
80 changes: 80 additions & 0 deletions internal/application/http_border_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import (
"testing"
)

func TestHttpBorderClient_Delete(t *testing.T) {
event := buildServerUpdateEvent(deletedEventType, ClientTypeHttp)
borderClient, nginxClient, err := buildBorderClient(ClientTypeHttp)
if err != nil {
t.Fatalf(`error occurred creating a new border client: %v`, err)
}

err = borderClient.Delete(event)
if err != nil {
t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err)
}

if !nginxClient.CalledFunctions["DeleteHTTPServer"] {
t.Fatalf(`expected DeleteHTTPServer to be called`)
}
}

func TestHttpBorderClient_Update(t *testing.T) {
event := buildServerUpdateEvent(createEventType, ClientTypeHttp)
borderClient, nginxClient, err := buildBorderClient(ClientTypeHttp)
if err != nil {
t.Fatalf(`error occurred creating a new border client: %v`, err)
}

err = borderClient.Update(event)
if err != nil {
t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err)
}

if !nginxClient.CalledFunctions["UpdateHTTPServers"] {
t.Fatalf(`expected UpdateHTTPServers to be called`)
}
}

func TestHttpBorderClient_BadNginxClient(t *testing.T) {
var emptyInterface interface{}
_, err := NewBorderClient(ClientTypeHttp, emptyInterface)
if err == nil {
t.Fatalf(`expected an error to occur when creating a new border client`)
}
}

func TestHttpBorderClient_DeleteReturnsError(t *testing.T) {
event := buildServerUpdateEvent(deletedEventType, ClientTypeHttp)
borderClient, _, err := buildTerrorizingBorderClient(ClientTypeHttp)
if err != nil {
t.Fatalf(`error occurred creating a new border client: %v`, err)
}

err = borderClient.Delete(event)

if err == nil {
t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`)
}
}

func TestHttpBorderClient_UpdateReturnsError(t *testing.T) {
event := buildServerUpdateEvent(createEventType, ClientTypeHttp)
borderClient, _, err := buildTerrorizingBorderClient(ClientTypeHttp)
if err != nil {
t.Fatalf(`error occurred creating a new border client: %v`, err)
}

err = borderClient.Update(event)

if err == nil {
t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`)
}
}
16 changes: 16 additions & 0 deletions internal/application/nginx_client_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import nginxClient "github.com/nginxinc/nginx-plus-go-client/client"

type NginxClientInterface interface {
DeleteStreamServer(upstream string, server string) error
UpdateStreamServers(upstream string, servers []nginxClient.StreamUpstreamServer) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error)

DeleteHTTPServer(upstream string, server string) error
UpdateHTTPServers(upstream string, servers []nginxClient.UpstreamServer) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error)
}
28 changes: 28 additions & 0 deletions internal/application/null_border_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import (
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
"github.com/sirupsen/logrus"
)

type NullBorderClient struct {
}

func NewNullBorderClient() (Interface, error) {
return &NullBorderClient{}, nil
}

func (nbc *NullBorderClient) Update(_ *core.ServerUpdateEvent) error {
logrus.Warn("NullBorderClient.Update called")
return nil
}

func (nbc *NullBorderClient) Delete(_ *core.ServerUpdateEvent) error {
logrus.Warn("NullBorderClient.Delete called")
return nil
}
24 changes: 24 additions & 0 deletions internal/application/null_border_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 F5 Inc. All rights reserved.
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
*/

package application

import "testing"

func TestNullBorderClient_Delete(t *testing.T) {
client := NullBorderClient{}
err := client.Delete(nil)
if err != nil {
t.Errorf(`expected no error deleting border client, got: %v`, err)
}
}

func TestNullBorderClient_Update(t *testing.T) {
client := NullBorderClient{}
err := client.Update(nil)
if err != nil {
t.Errorf(`expected no error updating border client, got: %v`, err)
}
}
Loading