Skip to content

Commit eedb6de

Browse files
authored
Issue 30 tcp http support (#31)
* BorderClient basics * Documentation updates * About to apply a broad rename * Rename to support library differences * Add HTTP Border Client * Add TCP Border Client * Use pointers * Synchronizer uses BorderClient * Translator extracts client type from Annotations, default when not found * Copy Paste issue, TCP Border Client need to use the *Stream methods * Fix stupid mistake in the refactor * Put an interface between NKL and the NGINX Plus client Upstream objects
1 parent c6b0849 commit eedb6de

24 files changed

+753
-124
lines changed

DESIGN.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ stateDiagram-v2
1717
Handler --> Translator
1818
Translator --> Handler
1919
Handler --> Synchronizer : "nkl-synchronizer queue"
20-
Synchronizer --> NGINXPlusLB1
21-
Synchronizer --> NGINXPlusLB2
22-
Synchronizer --> NGINXPlusLB...
23-
Synchronizer --> NGINXPlusLBn
20+
Synchronizer --> BorderClient : "HttpBorderClient | TcpBorderClient"
21+
BorderClient --> NGINXPlusLB1
22+
BorderClient --> NGINXPlusLB2
23+
BorderClient --> NGINXPlusLB...
24+
BorderClient --> NGINXPlusLBn
2425
```
2526

2627
### Settings
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import (
9+
"errors"
10+
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
11+
"github.com/nginxinc/kubernetes-nginx-ingress/test/mocks"
12+
)
13+
14+
const (
15+
deletedEventType = core.Deleted
16+
createEventType = core.Created
17+
upstreamName = "upstreamName"
18+
server = "server"
19+
)
20+
21+
func buildTerrorizingBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) {
22+
nginxClient := mocks.NewErroringMockClient(errors.New(`something went horribly horribly wrong`))
23+
bc, err := NewBorderClient(clientType, nginxClient)
24+
25+
return bc, nginxClient, err
26+
}
27+
28+
func buildBorderClient(clientType string) (Interface, *mocks.MockNginxClient, error) {
29+
nginxClient := mocks.NewMockNginxClient()
30+
bc, err := NewBorderClient(clientType, nginxClient)
31+
32+
return bc, nginxClient, err
33+
}
34+
35+
func buildServerUpdateEvent(eventType core.EventType, clientType string) *core.ServerUpdateEvent {
36+
upstreamServers := core.UpstreamServers{
37+
{
38+
Host: server,
39+
},
40+
}
41+
42+
return core.NewServerUpdateEvent(eventType, upstreamName, clientType, upstreamServers)
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright (c) 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
const (
9+
ClientTypeTcp = "tcp"
10+
ClientTypeHttp = "http"
11+
)

internal/application/border_client.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import (
9+
"fmt"
10+
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
11+
"github.com/sirupsen/logrus"
12+
)
13+
14+
type Interface interface {
15+
Update(*core.ServerUpdateEvent) error
16+
Delete(*core.ServerUpdateEvent) error
17+
}
18+
19+
type BorderClient struct {
20+
}
21+
22+
// NewBorderClient Returns a NullBorderClient if the type is unknown, this avoids panics due to nil pointer dereferences.
23+
func NewBorderClient(clientType string, borderClient interface{}) (Interface, error) {
24+
logrus.Debugf(`NewBorderClient for type: %s`, clientType)
25+
26+
switch clientType {
27+
case "tcp":
28+
return NewTcpBorderClient(borderClient)
29+
30+
case "http":
31+
return NewHttpBorderClient(borderClient)
32+
33+
default:
34+
borderClient, _ := NewNullBorderClient()
35+
return borderClient, fmt.Errorf(`unknown border client type: %s`, clientType)
36+
}
37+
}
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import (
9+
"github.com/nginxinc/kubernetes-nginx-ingress/test/mocks"
10+
"testing"
11+
)
12+
13+
func TestBorderClient_CreatesHttpBorderClient(t *testing.T) {
14+
borderClient := mocks.MockNginxClient{}
15+
client, err := NewBorderClient("http", borderClient)
16+
if err != nil {
17+
t.Errorf(`error creating border client: %v`, err)
18+
}
19+
20+
if _, ok := client.(*HttpBorderClient); !ok {
21+
t.Errorf(`expected client to be of type HttpBorderClient`)
22+
}
23+
}
24+
25+
func TestBorderClient_CreatesTcpBorderClient(t *testing.T) {
26+
borderClient := mocks.MockNginxClient{}
27+
client, err := NewBorderClient("tcp", borderClient)
28+
if err != nil {
29+
t.Errorf(`error creating border client: %v`, err)
30+
}
31+
32+
if _, ok := client.(*TcpBorderClient); !ok {
33+
t.Errorf(`expected client to be of type TcpBorderClient`)
34+
}
35+
}
36+
37+
func TestBorderClient_UnknownClientType(t *testing.T) {
38+
unknownClientType := "unknown"
39+
borderClient := mocks.MockNginxClient{}
40+
client, err := NewBorderClient(unknownClientType, borderClient)
41+
if err == nil {
42+
t.Errorf(`expected error creating border client`)
43+
}
44+
45+
if err.Error() != `unknown border client type: unknown` {
46+
t.Errorf(`expected error to be 'unknown border client type: unknown', got: %v`, err)
47+
}
48+
49+
if _, ok := client.(*NullBorderClient); !ok {
50+
t.Errorf(`expected client to be of type NullBorderClient`)
51+
}
52+
}

internal/application/doc.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
/*
7+
Package application includes support for applying updates to the Border servers.
8+
9+
"Border TcpServers" are the servers that are exposed to the outside world and direct traffic into the cluster.
10+
At this time the only supported Border TcpServers are NGINX Plus servers. The BorderClient module defines
11+
an interface that can be implemented to support other Border Server types.
12+
13+
- HttpBorderClient: updates NGINX Plus servers using HTTP Upstream methods on the NGINX Plus API.
14+
- TcpBorderClient: updates NGINX Plus servers using Stream Upstream methods on the NGINX Plus API.
15+
16+
Selection of the appropriate client is based on the Annotations present on the NodePort Service definition.
17+
*/
18+
19+
package application
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import (
9+
"fmt"
10+
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
11+
nginxClient "github.com/nginxinc/nginx-plus-go-client/client"
12+
)
13+
14+
type HttpBorderClient struct {
15+
BorderClient
16+
nginxClient NginxClientInterface
17+
}
18+
19+
func NewHttpBorderClient(client interface{}) (Interface, error) {
20+
ngxClient, ok := client.(NginxClientInterface)
21+
if !ok {
22+
return nil, fmt.Errorf(`expected a NginxClientInterface, got a %v`, client)
23+
}
24+
25+
return &HttpBorderClient{
26+
nginxClient: ngxClient,
27+
}, nil
28+
}
29+
30+
func (hbc *HttpBorderClient) Update(event *core.ServerUpdateEvent) error {
31+
httpUpstreamServers := asNginxHttpUpstreamServers(event.UpstreamServers)
32+
_, _, _, err := hbc.nginxClient.UpdateHTTPServers(event.UpstreamName, httpUpstreamServers)
33+
if err != nil {
34+
return fmt.Errorf(`error occurred updating the nginx+ upstream server: %w`, err)
35+
}
36+
37+
return nil
38+
}
39+
40+
func (hbc *HttpBorderClient) Delete(event *core.ServerUpdateEvent) error {
41+
err := hbc.nginxClient.DeleteHTTPServer(event.UpstreamName, event.UpstreamServers[0].Host)
42+
if err != nil {
43+
return fmt.Errorf(`error occurred deleting the nginx+ upstream server: %w`, err)
44+
}
45+
46+
return nil
47+
}
48+
49+
func asNginxHttpUpstreamServer(server *core.UpstreamServer) nginxClient.UpstreamServer {
50+
return nginxClient.UpstreamServer{
51+
Server: server.Host,
52+
}
53+
}
54+
55+
func asNginxHttpUpstreamServers(servers core.UpstreamServers) []nginxClient.UpstreamServer {
56+
var upstreamServers []nginxClient.UpstreamServer
57+
58+
for _, server := range servers {
59+
upstreamServers = append(upstreamServers, asNginxHttpUpstreamServer(server))
60+
}
61+
62+
return upstreamServers
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import (
9+
"testing"
10+
)
11+
12+
func TestHttpBorderClient_Delete(t *testing.T) {
13+
event := buildServerUpdateEvent(deletedEventType, ClientTypeHttp)
14+
borderClient, nginxClient, err := buildBorderClient(ClientTypeHttp)
15+
if err != nil {
16+
t.Fatalf(`error occurred creating a new border client: %v`, err)
17+
}
18+
19+
err = borderClient.Delete(event)
20+
if err != nil {
21+
t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err)
22+
}
23+
24+
if !nginxClient.CalledFunctions["DeleteHTTPServer"] {
25+
t.Fatalf(`expected DeleteHTTPServer to be called`)
26+
}
27+
}
28+
29+
func TestHttpBorderClient_Update(t *testing.T) {
30+
event := buildServerUpdateEvent(createEventType, ClientTypeHttp)
31+
borderClient, nginxClient, err := buildBorderClient(ClientTypeHttp)
32+
if err != nil {
33+
t.Fatalf(`error occurred creating a new border client: %v`, err)
34+
}
35+
36+
err = borderClient.Update(event)
37+
if err != nil {
38+
t.Fatalf(`error occurred deleting the nginx+ upstream server: %v`, err)
39+
}
40+
41+
if !nginxClient.CalledFunctions["UpdateHTTPServers"] {
42+
t.Fatalf(`expected UpdateHTTPServers to be called`)
43+
}
44+
}
45+
46+
func TestHttpBorderClient_BadNginxClient(t *testing.T) {
47+
var emptyInterface interface{}
48+
_, err := NewBorderClient(ClientTypeHttp, emptyInterface)
49+
if err == nil {
50+
t.Fatalf(`expected an error to occur when creating a new border client`)
51+
}
52+
}
53+
54+
func TestHttpBorderClient_DeleteReturnsError(t *testing.T) {
55+
event := buildServerUpdateEvent(deletedEventType, ClientTypeHttp)
56+
borderClient, _, err := buildTerrorizingBorderClient(ClientTypeHttp)
57+
if err != nil {
58+
t.Fatalf(`error occurred creating a new border client: %v`, err)
59+
}
60+
61+
err = borderClient.Delete(event)
62+
63+
if err == nil {
64+
t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`)
65+
}
66+
}
67+
68+
func TestHttpBorderClient_UpdateReturnsError(t *testing.T) {
69+
event := buildServerUpdateEvent(createEventType, ClientTypeHttp)
70+
borderClient, _, err := buildTerrorizingBorderClient(ClientTypeHttp)
71+
if err != nil {
72+
t.Fatalf(`error occurred creating a new border client: %v`, err)
73+
}
74+
75+
err = borderClient.Update(event)
76+
77+
if err == nil {
78+
t.Fatalf(`expected an error to occur when deleting the nginx+ upstream server`)
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import nginxClient "github.com/nginxinc/nginx-plus-go-client/client"
9+
10+
type NginxClientInterface interface {
11+
DeleteStreamServer(upstream string, server string) error
12+
UpdateStreamServers(upstream string, servers []nginxClient.StreamUpstreamServer) ([]nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, []nginxClient.StreamUpstreamServer, error)
13+
14+
DeleteHTTPServer(upstream string, server string) error
15+
UpdateHTTPServers(upstream string, servers []nginxClient.UpstreamServer) ([]nginxClient.UpstreamServer, []nginxClient.UpstreamServer, []nginxClient.UpstreamServer, error)
16+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import (
9+
"github.com/nginxinc/kubernetes-nginx-ingress/internal/core"
10+
"github.com/sirupsen/logrus"
11+
)
12+
13+
type NullBorderClient struct {
14+
}
15+
16+
func NewNullBorderClient() (Interface, error) {
17+
return &NullBorderClient{}, nil
18+
}
19+
20+
func (nbc *NullBorderClient) Update(_ *core.ServerUpdateEvent) error {
21+
logrus.Warn("NullBorderClient.Update called")
22+
return nil
23+
}
24+
25+
func (nbc *NullBorderClient) Delete(_ *core.ServerUpdateEvent) error {
26+
logrus.Warn("NullBorderClient.Delete called")
27+
return nil
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2023 F5 Inc. All rights reserved.
3+
* Use of this source code is governed by the Apache License that can be found in the LICENSE file.
4+
*/
5+
6+
package application
7+
8+
import "testing"
9+
10+
func TestNullBorderClient_Delete(t *testing.T) {
11+
client := NullBorderClient{}
12+
err := client.Delete(nil)
13+
if err != nil {
14+
t.Errorf(`expected no error deleting border client, got: %v`, err)
15+
}
16+
}
17+
18+
func TestNullBorderClient_Update(t *testing.T) {
19+
client := NullBorderClient{}
20+
err := client.Update(nil)
21+
if err != nil {
22+
t.Errorf(`expected no error updating border client, got: %v`, err)
23+
}
24+
}

0 commit comments

Comments
 (0)