Skip to content

Commit 497266d

Browse files
committed
Support processing multiple HTTP listeners
Problem: The Gateway needs to process a Gateway resource. Currently, that is already supported by the 'state' package. However, that support is limited. For example, the package doesn't allow processing multiple listeners. More over, the approach used in the 'state' package is hard to extend to support multiple listeners. This prompts development of a better solution. As a first deliverable, we want to support processing multiple HTTP listeners assuming they all bind to port 80. We also assume that the Gateway only supports a single Gateway resource with known namespace and name. Solution - replace the existing types of the state package with the new ones: - Introduce Configuration, which is an internal representation of the Gateway configuration. - Introduce Statuses, which holds status-related information about processed resources. - Introduce ChangeProcessor, which processes changes to Gateway API resources and returns new Configuration and Statuses.
1 parent e6bccad commit 497266d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3365
-2174
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ Before you can build and run the NGINX Kubernetes Gateway, make sure you have th
3434

3535
Set the `PREFIX` variable to the name of the registry you'd like to push the image to. By default, the image will be named `nginx-kubernetes-gateway:0.0.1`.
3636

37-
3837
1. Push the image to your container registry:
3938

4039
```
@@ -55,7 +54,6 @@ You can deploy NGINX Kubernetes Gateway on an existing Kubernetes 1.16+ cluster.
5554

5655
Make sure to substitute the image name with the name of the image you built.
5756

58-
5957
1. Install the Gateway CRDs:
6058

6159
```
@@ -89,6 +87,11 @@ You can deploy NGINX Kubernetes Gateway on an existing Kubernetes 1.16+ cluster.
8987
NAME READY STATUS RESTARTS AGE
9088
nginx-gateway-5d4f4c7db7-xk2kq 2/2 Running 0 112s
9189
```
90+
1. Create the Gateway resource:
91+
92+
```
93+
kubectl apply -f deploy/manifests/gateway.yaml
94+
```
9295
9396
## Expose NGINX Kubernetes Gateway
9497

cmd/gateway/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66

77
flag "github.com/spf13/pflag"
8+
"k8s.io/apimachinery/pkg/types"
89
"sigs.k8s.io/controller-runtime/pkg/log/zap"
910

1011
"github.com/nginxinc/nginx-kubernetes-gateway/internal/config"
@@ -36,6 +37,11 @@ func main() {
3637
conf := config.Config{
3738
GatewayCtlrName: *gatewayCtlrName,
3839
Logger: logger,
40+
// FIXME(pleshakov) Allow the cluster operator to customize this value
41+
GatewayNsName: types.NamespacedName{
42+
Namespace: "nginx-gateway",
43+
Name: "gateway",
44+
},
3945
}
4046

4147
MustValidateArguments(

deploy/manifests/gateway.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ metadata:
88
spec:
99
gatewayClassName: nginx
1010
listeners:
11-
- name: my-listener
12-
hostname: example.com
13-
port: 8080
11+
- name: http
12+
port: 80
1413
protocol: HTTP

deploy/manifests/nginx-gateway.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rules:
3636
- gateway.networking.k8s.io
3737
resources:
3838
- httproutes/status
39+
- gateways/status
3940
verbs:
4041
- update
4142
---

examples/advanced-routing/cafe-routes.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ kind: HTTPRoute
33
metadata:
44
name: cafe
55
spec:
6+
parentRefs:
7+
- name: gateway
8+
namespace: nginx-gateway
9+
sectionName: http
610
hostnames:
711
- "cafe.example.com"
812
rules:
@@ -15,6 +19,10 @@ kind: HTTPRoute
1519
metadata:
1620
name: coffee
1721
spec:
22+
parentRefs:
23+
- name: gateway
24+
namespace: nginx-gateway
25+
sectionName: http
1826
hostnames:
1927
- "cafe.example.com"
2028
rules:
@@ -37,6 +45,10 @@ kind: HTTPRoute
3745
metadata:
3846
name: tea
3947
spec:
48+
parentRefs:
49+
- name: gateway
50+
namespace: nginx-gateway
51+
sectionName: http
4052
hostnames:
4153
- "cafe.example.com"
4254
rules:

examples/cafe-example/cafe-routes.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ kind: HTTPRoute
33
metadata:
44
name: cafe
55
spec:
6+
parentRefs:
7+
- name: gateway
8+
namespace: nginx-gateway
9+
sectionName: http
610
hostnames:
711
- "cafe.example.com"
812
rules:
@@ -15,6 +19,10 @@ kind: HTTPRoute
1519
metadata:
1620
name: coffee
1721
spec:
22+
parentRefs:
23+
- name: gateway
24+
namespace: nginx-gateway
25+
sectionName: http
1826
hostnames:
1927
- "cafe.example.com"
2028
rules:
@@ -31,6 +39,10 @@ kind: HTTPRoute
3139
metadata:
3240
name: tea
3341
spec:
42+
parentRefs:
43+
- name: gateway
44+
namespace: nginx-gateway
45+
sectionName: http
3446
hostnames:
3547
- "cafe.example.com"
3648
rules:

internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package config
22

33
import (
44
"github.com/go-logr/logr"
5+
"k8s.io/apimachinery/pkg/types"
56
)
67

78
type Config struct {
89
GatewayCtlrName string
910
Logger logr.Logger
11+
// GatewayNsName is the namespaced name of a Gateway resource that the Gateway will use.
12+
// The Gateway will ignore all other Gateway resources.
13+
GatewayNsName types.NamespacedName
1014
}

internal/events/loop.go

Lines changed: 59 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,36 @@ import (
1717

1818
// EventLoop is the main event loop of the Gateway.
1919
type EventLoop struct {
20-
conf state.Configuration
20+
processor state.ChangeProcessor
2121
serviceStore state.ServiceStore
2222
generator config.Generator
2323
eventCh <-chan interface{}
2424
logger logr.Logger
25-
statusUpdater status.Updater
2625
nginxFileMgr file.Manager
2726
nginxRuntimeMgr runtime.Manager
27+
statusUpdater status.Updater
2828
}
2929

3030
// NewEventLoop creates a new EventLoop.
3131
func NewEventLoop(
32-
conf state.Configuration,
32+
processor state.ChangeProcessor,
3333
serviceStore state.ServiceStore,
3434
generator config.Generator,
3535
eventCh <-chan interface{},
36-
statusUpdater status.Updater,
3736
logger logr.Logger,
3837
nginxFileMgr file.Manager,
3938
nginxRuntimeMgr runtime.Manager,
39+
statusUpdater status.Updater,
4040
) *EventLoop {
4141
return &EventLoop{
42-
conf: conf,
42+
processor: processor,
4343
serviceStore: serviceStore,
4444
generator: generator,
4545
eventCh: eventCh,
46-
statusUpdater: statusUpdater,
4746
logger: logger.WithName("eventLoop"),
4847
nginxFileMgr: nginxFileMgr,
4948
nginxRuntimeMgr: nginxRuntimeMgr,
49+
statusUpdater: statusUpdater,
5050
}
5151
}
5252

@@ -58,112 +58,88 @@ func (el *EventLoop) Start(ctx context.Context) error {
5858
for {
5959
select {
6060
case <-ctx.Done():
61+
// although we always return nil, Start must return it to satisfy
62+
// "sigs.k8s.io/controller-runtime/pkg/manager".Runnable
6163
return nil
6264
case e := <-el.eventCh:
63-
err := el.handleEvent(ctx, e)
64-
if err != nil {
65-
return err
66-
}
65+
el.handleEvent(ctx, e)
6766
}
6867
}
6968
}
7069

7170
// FIXME(pleshakov): think about how to avoid using an interface{} here
72-
func (el *EventLoop) handleEvent(ctx context.Context, event interface{}) error {
73-
var changes []state.Change
74-
var updates []state.StatusUpdate
75-
var err error
76-
71+
func (el *EventLoop) handleEvent(ctx context.Context, event interface{}) {
7772
switch e := event.(type) {
7873
case *UpsertEvent:
79-
changes, updates, err = el.propagateUpsert(e)
74+
el.propagateUpsert(e)
8075
case *DeleteEvent:
81-
changes, updates, err = el.propagateDelete(e)
76+
el.propagateDelete(e)
8277
default:
83-
// FIXME(pleshakov): panic because it is a coding error
84-
return fmt.Errorf("unknown event type %T", e)
78+
panic(fmt.Errorf("unknown event type %T", e))
79+
}
80+
81+
changed, conf, statuses := el.processor.Process()
82+
if !changed {
83+
return
8584
}
8685

86+
err := el.updateNginx(ctx, conf)
87+
if err != nil {
88+
el.logger.Error(err, "Failed to update NGINX configuration")
89+
}
90+
91+
el.statusUpdater.Update(ctx, statuses)
92+
}
93+
94+
func (el *EventLoop) updateNginx(ctx context.Context, conf state.Configuration) error {
95+
cfg, warnings := el.generator.Generate(conf)
96+
97+
// For now, we keep all http servers in one config
98+
// We might rethink that. For example, we can write each server to its file
99+
// or group servers in some way.
100+
err := el.nginxFileMgr.WriteHTTPServersConfig("http-servers", cfg)
87101
if err != nil {
88102
return err
89103
}
90104

91-
el.processChangesAndStatusUpdates(ctx, changes, updates)
92-
return nil
105+
for obj, objWarnings := range warnings {
106+
for _, w := range objWarnings {
107+
// FIXME(pleshakov): report warnings via Object status
108+
el.logger.Info("got warning while generating config",
109+
"kind", obj.GetObjectKind().GroupVersionKind().Kind,
110+
"namespace", obj.GetNamespace(),
111+
"name", obj.GetName(),
112+
"warning", w)
113+
}
114+
}
115+
116+
return el.nginxRuntimeMgr.Reload(ctx)
93117
}
94118

95-
func (el *EventLoop) propagateUpsert(e *UpsertEvent) ([]state.Change, []state.StatusUpdate, error) {
119+
func (el *EventLoop) propagateUpsert(e *UpsertEvent) {
96120
switch r := e.Resource.(type) {
121+
case *v1alpha2.Gateway:
122+
el.processor.CaptureUpsertChange(r)
97123
case *v1alpha2.HTTPRoute:
98-
changes, statusUpdates := el.conf.UpsertHTTPRoute(r)
99-
return changes, statusUpdates, nil
124+
el.processor.CaptureUpsertChange(r)
100125
case *apiv1.Service:
101-
el.serviceStore.Upsert(r)
102126
// FIXME(pleshakov): make sure the affected hosts are updated
103-
return nil, nil, nil
127+
el.serviceStore.Upsert(r)
128+
default:
129+
panic(fmt.Errorf("unknown resource type %T", e.Resource))
104130
}
105-
106-
// FIXME(pleshakov): panic because it is a coding error
107-
return nil, nil, fmt.Errorf("unknown resource type %T", e.Resource)
108131
}
109132

110-
func (el *EventLoop) propagateDelete(e *DeleteEvent) ([]state.Change, []state.StatusUpdate, error) {
133+
func (el *EventLoop) propagateDelete(e *DeleteEvent) {
111134
switch e.Type.(type) {
135+
case *v1alpha2.Gateway:
136+
el.processor.CaptureDeleteChange(e.Type, e.NamespacedName)
112137
case *v1alpha2.HTTPRoute:
113-
changes, statusUpdates := el.conf.DeleteHTTPRoute(e.NamespacedName)
114-
return changes, statusUpdates, nil
138+
el.processor.CaptureDeleteChange(e.Type, e.NamespacedName)
115139
case *apiv1.Service:
116-
el.serviceStore.Delete(e.NamespacedName)
117140
// FIXME(pleshakov): make sure the affected hosts are updated
118-
return nil, nil, nil
119-
}
120-
121-
// FIXME(pleshakov): panic because it is a coding error
122-
return nil, nil, fmt.Errorf("unknown resource type %T", e.Type)
123-
}
124-
125-
func (el *EventLoop) processChangesAndStatusUpdates(ctx context.Context, changes []state.Change, updates []state.StatusUpdate) {
126-
for _, c := range changes {
127-
el.logger.Info("Processing a change",
128-
"host", c.Host.Value)
129-
130-
if c.Op == state.Upsert {
131-
cfg, warnings := el.generator.GenerateForHost(c.Host)
132-
133-
for obj, objWarnings := range warnings {
134-
for _, w := range objWarnings {
135-
// FIXME(pleshakov): report warnings via Object status
136-
el.logger.Info("got warning while generating config",
137-
"kind", obj.GetObjectKind().GroupVersionKind().Kind,
138-
"namespace", obj.GetNamespace(),
139-
"name", obj.GetName(),
140-
"warning", w)
141-
}
142-
}
143-
144-
el.logger.Info("Writing configuration",
145-
"host", c.Host.Value)
146-
147-
err := el.nginxFileMgr.WriteServerConfig(c.Host.Value, cfg)
148-
if err != nil {
149-
el.logger.Error(err, "Failed to write configuration",
150-
"host", c.Host.Value)
151-
}
152-
} else {
153-
err := el.nginxFileMgr.DeleteServerConfig(c.Host.Value)
154-
if err != nil {
155-
el.logger.Error(err, "Failed to delete configuration",
156-
"host", c.Host.Value)
157-
}
158-
}
159-
}
160-
161-
if len(changes) > 0 {
162-
err := el.nginxRuntimeMgr.Reload(ctx)
163-
if err != nil {
164-
el.logger.Error(err, "Failed to reload NGINX")
165-
}
141+
el.serviceStore.Delete(e.NamespacedName)
142+
default:
143+
panic(fmt.Errorf("unknown resource type %T", e.Type))
166144
}
167-
168-
el.statusUpdater.ProcessStatusUpdates(ctx, updates)
169145
}

0 commit comments

Comments
 (0)