Skip to content

Commit e15b59f

Browse files
committed
Update statuses of HTTPRoutes
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 e15b59f

File tree

11 files changed

+558
-76
lines changed

11 files changed

+558
-76
lines changed

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: 15 additions & 22 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
@@ -62,9 +65,7 @@ func (el *EventLoop) handleEvent(event interface{}) error {
6265
return err
6366
}
6467

65-
el.processChangesAndStatusUpdates(changes, updates)
66-
67-
return nil
68+
return el.processChangesAndStatusUpdates(ctx, changes, updates)
6869
}
6970

7071
func (el *EventLoop) propagateUpsert(e *UpsertEvent) ([]state.Change, []state.StatusUpdate, error) {
@@ -87,7 +88,7 @@ func (el *EventLoop) propagateDelete(e *DeleteEvent) ([]state.Change, []state.St
8788
return nil, nil, fmt.Errorf("unknown resource type %T", e.Type)
8889
}
8990

90-
func (el *EventLoop) processChangesAndStatusUpdates(changes []state.Change, updates []state.StatusUpdate) {
91+
func (el *EventLoop) processChangesAndStatusUpdates(ctx context.Context, changes []state.Change, updates []state.StatusUpdate) error {
9192
for _, c := range changes {
9293
el.logger.Info("Processing a change",
9394
"host", c.Host.Value)
@@ -96,13 +97,5 @@ func (el *EventLoop) processChangesAndStatusUpdates(changes []state.Change, upda
9697
fmt.Printf("%+v\n", c)
9798
}
9899

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-
}
100+
return el.statusUpdater.ProcessStatusUpdates(ctx, updates)
108101
}

internal/events/loop_test.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package events_test
22

33
import (
44
"context"
5+
"errors"
56

67
"github.com/nginxinc/nginx-gateway-kubernetes/internal/events"
8+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
79
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state/statefakes"
10+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status/statusfakes"
811
. "github.com/onsi/ginkgo/v2"
912
. "github.com/onsi/gomega"
1013
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -30,14 +33,16 @@ func (r *unsupportedResource) DeepCopyObject() runtime.Object {
3033
var _ = Describe("EventLoop", func() {
3134
var ctrl *events.EventLoop
3235
var fakeConf *statefakes.FakeConfiguration
36+
var fakeUpdater *statusfakes.FakeUpdater
3337
var cancel context.CancelFunc
3438
var eventCh chan interface{}
3539
var errorCh chan error
3640

3741
BeforeEach(func() {
3842
fakeConf = &statefakes.FakeConfiguration{}
3943
eventCh = make(chan interface{})
40-
ctrl = events.NewEventLoop(fakeConf, eventCh, zap.New())
44+
fakeUpdater = &statusfakes.FakeUpdater{}
45+
ctrl = events.NewEventLoop(fakeConf, eventCh, fakeUpdater, zap.New())
4146

4247
var ctx context.Context
4348

@@ -59,6 +64,16 @@ var _ = Describe("EventLoop", func() {
5964
})
6065

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

6479
eventCh <- &events.UpsertEvent{
@@ -69,9 +84,25 @@ var _ = Describe("EventLoop", func() {
6984
Eventually(func() *v1alpha2.HTTPRoute {
7085
return fakeConf.UpsertHTTPRouteArgsForCall(0)
7186
}).Should(Equal(hr))
87+
88+
Eventually(fakeUpdater.ProcessStatusUpdatesCallCount()).Should(Equal(1))
89+
Eventually(func() []state.StatusUpdate {
90+
_, updates := fakeUpdater.ProcessStatusUpdatesArgsForCall(0)
91+
return updates
92+
}).Should(Equal(fakeStatusUpdates))
7293
})
7394

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

77108
eventCh <- &events.DeleteEvent{
@@ -83,6 +114,12 @@ var _ = Describe("EventLoop", func() {
83114
Eventually(func() types.NamespacedName {
84115
return fakeConf.DeleteHTTPRouteArgsForCall(0)
85116
}).Should(Equal(nsname))
117+
118+
Eventually(fakeUpdater.ProcessStatusUpdatesCallCount()).Should(Equal(1))
119+
Eventually(func() []state.StatusUpdate {
120+
_, updates := fakeUpdater.ProcessStatusUpdatesArgsForCall(0)
121+
return updates
122+
}).Should(Equal(fakeStatusUpdates))
86123
})
87124
})
88125

@@ -110,5 +147,18 @@ var _ = Describe("EventLoop", func() {
110147
Type: &unsupportedResource{},
111148
}),
112149
)
150+
151+
It("should return error if status updater returns error", func() {
152+
testError := errors.New("test")
153+
fakeUpdater.ProcessStatusUpdatesReturns(testError)
154+
155+
eventCh <- &events.UpsertEvent{
156+
Resource: &v1alpha2.HTTPRoute{},
157+
}
158+
159+
var err error
160+
Eventually(errorCh).Should(Receive(&err))
161+
Expect(err).Should(Equal(testError))
162+
})
113163
})
114164
})

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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
gcfg "github.com/nginxinc/nginx-gateway-kubernetes/internal/implementations/gatewayconfig"
1111
hr "github.com/nginxinc/nginx-gateway-kubernetes/internal/implementations/httproute"
1212
"github.com/nginxinc/nginx-gateway-kubernetes/internal/state"
13+
"github.com/nginxinc/nginx-gateway-kubernetes/internal/status"
1314
nginxgwv1alpha1 "github.com/nginxinc/nginx-gateway-kubernetes/pkg/apis/gateway/v1alpha1"
1415
"github.com/nginxinc/nginx-gateway-kubernetes/pkg/sdk"
1516

@@ -58,11 +59,12 @@ func Start(cfg config.Config) error {
5859
}
5960

6061
conf := state.NewConfiguration(cfg.GatewayCtlrName, state.NewRealClock())
61-
mainCtrl := events.NewEventLoop(conf, eventCh, cfg.Logger)
62+
reporter := status.NewUpdater(mgr.GetClient(), cfg.Logger)
63+
eventLoop := events.NewEventLoop(conf, eventCh, reporter, cfg.Logger)
6264

63-
err = mgr.Add(mainCtrl)
65+
err = mgr.Add(eventLoop)
6466
if err != nil {
65-
return fmt.Errorf("cannot register main controller")
67+
return fmt.Errorf("cannot register event loop")
6668
}
6769

6870
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)