Skip to content

Commit 6bb67d0

Browse files
authored
Update statuses of HTTPRoutes (#60)
This commit adds a new component - status.Updater - which updates statuses of HTTPRoutes. In the future, it will update statuses of the other supported resources. The added component has a few limitations (described in the comments) that are required to be addressed before the Gateway is considered to be ready for production use.
1 parent 889cfad commit 6bb67d0

File tree

12 files changed

+549
-77
lines changed

12 files changed

+549
-77
lines changed

cmd/gateway/setup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func MockValidator(name string, called *int, succeed bool) ValidatorContext {
1919
*called++
2020

2121
if !succeed {
22-
return errors.New("Mock error")
22+
return errors.New("mock error")
2323
}
2424
return nil
2525
},

deploy/manifests/nginx-gateway.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ rules:
3030
verbs:
3131
- list
3232
- watch
33+
- apiGroups:
34+
- gateway.networking.k8s.io
35+
resources:
36+
- httproutes/status
37+
verbs:
38+
- update
3339
---
3440
kind: ClusterRoleBinding
3541
apiVersion: rbac.authorization.k8s.io/v1

internal/events/loop.go

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@ import (
66

77
"github.com/go-logr/logr"
88
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
9+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status"
910
"sigs.k8s.io/gateway-api/apis/v1alpha2"
1011
)
1112

1213
// EventLoop is the main event loop of the Gateway.
1314
type EventLoop struct {
14-
conf state.Configuration
15-
eventCh <-chan interface{}
16-
logger logr.Logger
15+
conf state.Configuration
16+
eventCh <-chan interface{}
17+
logger logr.Logger
18+
statusUpdater status.Updater
1719
}
1820

1921
// NewEventLoop creates a new EventLoop.
20-
func NewEventLoop(conf state.Configuration, eventCh <-chan interface{}, logger logr.Logger) *EventLoop {
22+
func NewEventLoop(conf state.Configuration, eventCh <-chan interface{}, statusUpdater status.Updater, logger logr.Logger) *EventLoop {
2123
return &EventLoop{
22-
conf: conf,
23-
eventCh: eventCh,
24-
logger: logger.WithName("eventLoop"),
24+
conf: conf,
25+
eventCh: eventCh,
26+
statusUpdater: statusUpdater,
27+
logger: logger.WithName("eventLoop"),
2528
}
2629
}
2730

@@ -35,7 +38,7 @@ func (el *EventLoop) Start(ctx context.Context) error {
3538
case <-ctx.Done():
3639
return nil
3740
case e := <-el.eventCh:
38-
err := el.handleEvent(e)
41+
err := el.handleEvent(ctx, e)
3942
if err != nil {
4043
return err
4144
}
@@ -44,7 +47,7 @@ func (el *EventLoop) Start(ctx context.Context) error {
4447
}
4548

4649
// TO-DO: think about how to avoid using an interface{} here
47-
func (el *EventLoop) handleEvent(event interface{}) error {
50+
func (el *EventLoop) handleEvent(ctx context.Context, event interface{}) error {
4851
var changes []state.Change
4952
var updates []state.StatusUpdate
5053
var err error
@@ -55,15 +58,15 @@ func (el *EventLoop) handleEvent(event interface{}) error {
5558
case *DeleteEvent:
5659
changes, updates, err = el.propagateDelete(e)
5760
default:
61+
// TO-DO: panic
5862
return fmt.Errorf("unknown event type %T", e)
5963
}
6064

6165
if err != nil {
6266
return err
6367
}
6468

65-
el.processChangesAndStatusUpdates(changes, updates)
66-
69+
el.processChangesAndStatusUpdates(ctx, changes, updates)
6770
return nil
6871
}
6972

@@ -87,7 +90,7 @@ func (el *EventLoop) propagateDelete(e *DeleteEvent) ([]state.Change, []state.St
8790
return nil, nil, fmt.Errorf("unknown resource type %T", e.Type)
8891
}
8992

90-
func (el *EventLoop) processChangesAndStatusUpdates(changes []state.Change, updates []state.StatusUpdate) {
93+
func (el *EventLoop) processChangesAndStatusUpdates(ctx context.Context, changes []state.Change, updates []state.StatusUpdate) {
9194
for _, c := range changes {
9295
el.logger.Info("Processing a change",
9396
"host", c.Host.Value)
@@ -96,13 +99,5 @@ func (el *EventLoop) processChangesAndStatusUpdates(changes []state.Change, upda
9699
fmt.Printf("%+v\n", c)
97100
}
98101

99-
for _, u := range updates {
100-
// TO-DO: in the next iteration, the update will include the namespace/name of the resource instead of
101-
// runtime.Object, so it will be easy to get the resource namespace/name and include it in the log output
102-
el.logger.Info("Processing a status update",
103-
"gvk", u.Object.GetObjectKind().GroupVersionKind().String())
104-
105-
// TO-DO: This code is temporary. We will remove it once we have a component that updates statuses.
106-
fmt.Printf("%+v\n", u)
107-
}
102+
el.statusUpdater.ProcessStatusUpdates(ctx, updates)
108103
}

internal/events/loop_test.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"context"
55

66
"github.com/nginxinc/nginx-gateway-kubernetes/internal/events"
7+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
78
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state/statefakes"
9+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status/statusfakes"
810
. "github.com/onsi/ginkgo/v2"
911
. "github.com/onsi/gomega"
1012
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,14 +32,16 @@ func (r *unsupportedResource) DeepCopyObject() runtime.Object {
3032
var _ = Describe("EventLoop", func() {
3133
var ctrl *events.EventLoop
3234
var fakeConf *statefakes.FakeConfiguration
35+
var fakeUpdater *statusfakes.FakeUpdater
3336
var cancel context.CancelFunc
3437
var eventCh chan interface{}
3538
var errorCh chan error
3639

3740
BeforeEach(func() {
3841
fakeConf = &statefakes.FakeConfiguration{}
3942
eventCh = make(chan interface{})
40-
ctrl = events.NewEventLoop(fakeConf, eventCh, zap.New())
43+
fakeUpdater = &statusfakes.FakeUpdater{}
44+
ctrl = events.NewEventLoop(fakeConf, eventCh, fakeUpdater, zap.New())
4145

4246
var ctx context.Context
4347

@@ -59,6 +63,16 @@ var _ = Describe("EventLoop", func() {
5963
})
6064

6165
It("should process upsert event", func() {
66+
fakeStatusUpdates := []state.StatusUpdate{
67+
{
68+
NamespacedName: types.NamespacedName{},
69+
Status: nil,
70+
},
71+
}
72+
// for now, we pass nil, because we don't need to test how EventLoop processes changes yet. We will start
73+
// testing once we have NGINX Configuration Manager component.
74+
fakeConf.UpsertHTTPRouteReturns(nil, fakeStatusUpdates)
75+
6276
hr := &v1alpha2.HTTPRoute{}
6377

6478
eventCh <- &events.UpsertEvent{
@@ -69,9 +83,25 @@ var _ = Describe("EventLoop", func() {
6983
Eventually(func() *v1alpha2.HTTPRoute {
7084
return fakeConf.UpsertHTTPRouteArgsForCall(0)
7185
}).Should(Equal(hr))
86+
87+
Eventually(fakeUpdater.ProcessStatusUpdatesCallCount()).Should(Equal(1))
88+
Eventually(func() []state.StatusUpdate {
89+
_, updates := fakeUpdater.ProcessStatusUpdatesArgsForCall(0)
90+
return updates
91+
}).Should(Equal(fakeStatusUpdates))
7292
})
7393

7494
It("should process delete event", func() {
95+
fakeStatusUpdates := []state.StatusUpdate{
96+
{
97+
NamespacedName: types.NamespacedName{},
98+
Status: nil,
99+
},
100+
}
101+
// for now, we pass nil, because we don't need to test how EventLoop processes changes yet. We will start
102+
// testing once we have NGINX Configuration Manager component.
103+
fakeConf.DeleteHTTPRouteReturns(nil, fakeStatusUpdates)
104+
75105
nsname := types.NamespacedName{Namespace: "test", Name: "route"}
76106

77107
eventCh <- &events.DeleteEvent{
@@ -83,6 +113,12 @@ var _ = Describe("EventLoop", func() {
83113
Eventually(func() types.NamespacedName {
84114
return fakeConf.DeleteHTTPRouteArgsForCall(0)
85115
}).Should(Equal(nsname))
116+
117+
Eventually(fakeUpdater.ProcessStatusUpdatesCallCount()).Should(Equal(1))
118+
Eventually(func() []state.StatusUpdate {
119+
_, updates := fakeUpdater.ProcessStatusUpdatesArgsForCall(0)
120+
return updates
121+
}).Should(Equal(fakeStatusUpdates))
86122
})
87123
})
88124

internal/helpers/helpers.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package helpers
2+
3+
import "github.com/google/go-cmp/cmp"
4+
5+
// Diff prints the diff between two structs.
6+
// It is useful in testing to compare two structs when they are large. In such a case, without Diff it will be difficult
7+
// to pinpoint the difference between the two structs.
8+
func Diff(x, y interface{}) string {
9+
r := cmp.Diff(x, y)
10+
11+
if r != "" {
12+
return "(-want +got)\n" + r
13+
}
14+
return r
15+
}

internal/manager/manager.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package manager
22

33
import (
44
"fmt"
5+
"time"
56

67
"github.com/nginxinc/nginx-gateway-kubernetes/internal/config"
78
"github.com/nginxinc/nginx-gateway-kubernetes/internal/events"
@@ -10,6 +11,7 @@ import (
1011
gcfg "github.com/nginxinc/nginx-gateway-kubernetes/internal/implementations/gatewayconfig"
1112
hr "github.com/nginxinc/nginx-gateway-kubernetes/internal/implementations/httproute"
1213
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
14+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status"
1315
nginxgwv1alpha1 "github.com/nginxinc/nginx-gateway-kubernetes/pkg/apis/gateway/v1alpha1"
1416
"github.com/nginxinc/nginx-gateway-kubernetes/pkg/sdk"
1517

@@ -19,6 +21,9 @@ import (
1921
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
2022
)
2123

24+
// clusterTimeout is a timeout for connections to the Kubernetes API
25+
const clusterTimeout = 10 * time.Second
26+
2227
var scheme = runtime.NewScheme()
2328

2429
func init() {
@@ -35,7 +40,10 @@ func Start(cfg config.Config) error {
3540

3641
eventCh := make(chan interface{})
3742

38-
mgr, err := manager.New(ctlr.GetConfigOrDie(), options)
43+
clusterCfg := ctlr.GetConfigOrDie()
44+
clusterCfg.Timeout = clusterTimeout
45+
46+
mgr, err := manager.New(clusterCfg, options)
3947
if err != nil {
4048
return fmt.Errorf("cannot build runtime manager: %w", err)
4149
}
@@ -58,11 +66,12 @@ func Start(cfg config.Config) error {
5866
}
5967

6068
conf := state.NewConfiguration(cfg.GatewayCtlrName, state.NewRealClock())
61-
mainCtrl := events.NewEventLoop(conf, eventCh, cfg.Logger)
69+
reporter := status.NewUpdater(mgr.GetClient(), cfg.Logger)
70+
eventLoop := events.NewEventLoop(conf, eventCh, reporter, cfg.Logger)
6271

63-
err = mgr.Add(mainCtrl)
72+
err = mgr.Add(eventLoop)
6473
if err != nil {
65-
return fmt.Errorf("cannot register main controller")
74+
return fmt.Errorf("cannot register event loop: %w", err)
6675
}
6776

6877
ctx := ctlr.SetupSignalHandler()

internal/state/configuration.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"strings"
77

88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9-
"k8s.io/apimachinery/pkg/runtime"
109
"k8s.io/apimachinery/pkg/types"
1110
"sigs.k8s.io/gateway-api/apis/v1alpha2"
1211
)
@@ -148,8 +147,8 @@ type Change struct {
148147

149148
// StatusUpdate represents an update to the status of a resource.
150149
type StatusUpdate struct {
151-
// Object is the resource.
152-
Object runtime.Object
150+
// NamespacedName is the NamespacedName of the resource.
151+
NamespacedName types.NamespacedName
153152
// Status is the status field of the resource
154153
// The Status include only the new conditions. This means that the status reporter component will need to merge
155154
// the new conditions with the existing conditions of the resource.
@@ -235,12 +234,16 @@ func (c *configurationImpl) updateListeners() ([]Change, []StatusUpdate) {
235234
// TO-DO: optimize it so that we only update the status of the affected (changed) httpRoutes
236235
// getSortedKeys is used to ensure predictable order for unit tests
237236
for _, key := range getSortedKeys(listener.httpRoutes) {
237+
route := listener.httpRoutes[key]
238238
update := StatusUpdate{
239-
Object: listener.httpRoutes[key],
239+
NamespacedName: types.NamespacedName{Namespace: route.Namespace, Name: route.Name},
240240
Status: &v1alpha2.HTTPRouteStatus{
241241
RouteStatus: v1alpha2.RouteStatus{
242242
Parents: []v1alpha2.RouteParentStatus{
243243
{
244+
ParentRef: v1alpha2.ParentRef{
245+
Name: "fake", // TO-DO: report the parent ref properly
246+
},
244247
ControllerName: v1alpha2.GatewayController(c.gatewayCtlrName),
245248
Conditions: []metav1.Condition{
246249
{

0 commit comments

Comments
 (0)