Skip to content

Commit 1487d62

Browse files
davidjumanisoloio-bulldozer[bot]
andauthoredOct 28, 2024··
[1.17] fix: Handle errors in ggv2 plugins when resource is not in a watched namespace (#10242)
Co-authored-by: soloio-bulldozer[bot] <48420018+soloio-bulldozer[bot]@users.noreply.github.com>

File tree

5 files changed

+133
-8
lines changed

5 files changed

+133
-8
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
changelog:
2+
- type: FIX
3+
issueLink: https://github.com/solo-io/solo-projects/issues/7082
4+
resolvesIssue: false
5+
description: Fixes a bug where gloo segfaults if resources are applied to a unwatched namespace.
6+

‎projects/gateway2/translator/plugins/routeoptions/route_options_plugin.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"errors"
66
"fmt"
77

8+
"github.com/hashicorp/go-multierror"
9+
"github.com/rotisserie/eris"
810
"github.com/solo-io/gloo/projects/gloo/pkg/xds"
911
"github.com/solo-io/go-utils/contextutils"
1012
"github.com/solo-io/solo-kit/pkg/api/v1/clients"
@@ -31,6 +33,8 @@ import (
3133
var (
3234
_ plugins.RoutePlugin = &plugin{}
3335
_ plugins.StatusPlugin = &plugin{}
36+
37+
ReadingRouteOptionErrStr = "error reading RouteOption"
3438
)
3539

3640
// holds the data structures needed to derive and report a classic GE status
@@ -133,9 +137,14 @@ func (p *plugin) ApplyStatusPlugin(ctx context.Context, statusCtx *plugins.Statu
133137
}
134138
}
135139
routeOptionReport := make(reporter.ResourceReports)
140+
var multierr *multierror.Error
136141
for roKey, status := range p.legacyStatusCache {
137142
// get the obj by namespacedName
138-
roObj, _ := p.routeOptionClient.Read(roKey.Namespace, roKey.Name, clients.ReadOpts{Ctx: ctx})
143+
roObj, err := p.routeOptionClient.Read(roKey.Namespace, roKey.Name, clients.ReadOpts{Ctx: ctx})
144+
if err != nil {
145+
multierr = multierror.Append(multierr, eris.Wrapf(err, "%s %s in namespace %s", ReadingRouteOptionErrStr, roKey.Name, roKey.Namespace))
146+
continue
147+
}
139148

140149
// mark this object to be processed
141150
routeOptionReport.Accept(roObj)
@@ -146,12 +155,13 @@ func (p *plugin) ApplyStatusPlugin(ctx context.Context, statusCtx *plugins.Statu
146155
}
147156

148157
// actually write out the reports!
149-
err := p.statusReporter.WriteReports(ctx, routeOptionReport, status.subresourceStatus)
158+
err = p.statusReporter.WriteReports(ctx, routeOptionReport, status.subresourceStatus)
150159
if err != nil {
151-
return fmt.Errorf("error writing status report from RouteOptionPlugin: %w", err)
160+
multierr = multierror.Append(multierr, fmt.Errorf("error writing status report from RouteOptionPlugin: %w", err))
161+
continue
152162
}
153163
}
154-
return nil
164+
return multierr.ErrorOrNil()
155165
}
156166

157167
// tracks the attachment of a RouteOption so we know which RouteOptions to report status for

‎projects/gateway2/translator/plugins/routeoptions/route_options_plugin_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ var _ = Describe("RouteOptionsPlugin", func() {
4242
cancel context.CancelFunc
4343
routeOptionClient sologatewayv1.RouteOptionClient
4444
statusReporter reporter.StatusReporter
45+
statusCtx *plugins.StatusContext
4546
)
4647

4748
BeforeEach(func() {
@@ -259,6 +260,81 @@ var _ = Describe("RouteOptionsPlugin", func() {
259260
})
260261
})
261262

263+
Context("There is an error reading the RouteOptions", func() {
264+
BeforeEach(func() {
265+
statusCtx = &plugins.StatusContext{
266+
ProxiesWithReports: []translatorutils.ProxyWithReports{
267+
{
268+
Proxy: &v1.Proxy{},
269+
Reports: translatorutils.TranslationReports{
270+
ProxyReport: &validation.ProxyReport{},
271+
ResourceReports: reporter.ResourceReports{},
272+
},
273+
},
274+
},
275+
}
276+
})
277+
278+
When("The RouteOption has a TargetRef", func() {
279+
It("errors out", func() {
280+
deps := []client.Object{attachedRouteOption()}
281+
fakeClient := testutils.BuildIndexedFakeClient(deps, gwquery.IterateIndices, rtoptquery.IterateIndices)
282+
gwQueries := testutils.BuildGatewayQueriesWithClient(fakeClient)
283+
plugin := NewPlugin(gwQueries, fakeClient, routeOptionClient, statusReporter)
284+
285+
ctx := context.Background()
286+
routeCtx := &plugins.RouteContext{
287+
Route: &gwv1.HTTPRoute{
288+
ObjectMeta: metav1.ObjectMeta{
289+
Name: "route",
290+
Namespace: "default",
291+
},
292+
},
293+
}
294+
295+
outputRoute := &v1.Route{
296+
Options: &v1.RouteOptions{},
297+
}
298+
plugin.ApplyRoutePlugin(ctx, routeCtx, outputRoute)
299+
300+
err := plugin.ApplyStatusPlugin(ctx, statusCtx)
301+
Expect(err).To(MatchError(ContainSubstring(ReadingRouteOptionErrStr)))
302+
})
303+
})
304+
305+
When("The HTTPRoute has an ExtensionRef", func() {
306+
It("errors out", func() {
307+
deps := []client.Object{routeOption()}
308+
fakeClient := testutils.BuildIndexedFakeClient(deps, gwquery.IterateIndices, rtoptquery.IterateIndices)
309+
gwQueries := testutils.BuildGatewayQueriesWithClient(fakeClient)
310+
plugin := NewPlugin(gwQueries, fakeClient, routeOptionClient, statusReporter)
311+
312+
rtCtx := &plugins.RouteContext{
313+
Route: &gwv1.HTTPRoute{},
314+
Rule: &gwv1.HTTPRouteRule{
315+
Filters: []gwv1.HTTPRouteFilter{{
316+
Type: gwv1.HTTPRouteFilterExtensionRef,
317+
ExtensionRef: &gwv1.LocalObjectReference{
318+
Group: gwv1.Group(sologatewayv1.RouteOptionGVK.Group),
319+
Kind: gwv1.Kind(sologatewayv1.RouteOptionGVK.Kind),
320+
Name: "filter-policy",
321+
},
322+
}},
323+
},
324+
}
325+
326+
outputRoute := &v1.Route{
327+
Options: &v1.RouteOptions{},
328+
}
329+
plugin.ApplyRoutePlugin(context.Background(), rtCtx, outputRoute)
330+
331+
err := plugin.ApplyStatusPlugin(ctx, statusCtx)
332+
Expect(err).To(MatchError(ContainSubstring(ReadingRouteOptionErrStr)))
333+
})
334+
335+
})
336+
})
337+
262338
When("Two RouteOptions are attached correctly with different creation timestamps", func() {
263339
It("correctly adds faultinjection from the earliest created object", func() {
264340
routeOptionClient.Write(attachedInternal(), clients.WriteOpts{})

‎projects/gateway2/translator/plugins/virtualhostoptions/virtualhost_options_plugin.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8+
"github.com/hashicorp/go-multierror"
89
"github.com/rotisserie/eris"
910
"github.com/solo-io/go-utils/contextutils"
1011
"github.com/solo-io/solo-kit/pkg/api/v1/clients"
@@ -30,6 +31,8 @@ import (
3031
var (
3132
_ plugins.ListenerPlugin = &plugin{}
3233
_ plugins.StatusPlugin = &plugin{}
34+
35+
ReadingVirtualHostOptionErrStr = "error reading VirtualHostOption"
3336
)
3437

3538
type plugin struct {
@@ -162,6 +165,7 @@ func optionsToStr(opts []*solokubev1.VirtualHostOption) string {
162165

163166
// Add all statuses for processed VirtualHostOptions. These could come from the VHO itself or
164167
// or any VH to which it is attached.
168+
// It returns the aggregated errors reading the VirtualHostOptions if any.
165169
func (p *plugin) ApplyStatusPlugin(ctx context.Context, statusCtx *plugins.StatusContext) error {
166170
logger := contextutils.LoggerFrom(ctx)
167171
// gather all VirtualHostOptions we need to report status for
@@ -199,9 +203,14 @@ func (p *plugin) ApplyStatusPlugin(ctx context.Context, statusCtx *plugins.Statu
199203
}
200204
virtualHostOptionReport := make(reporter.ResourceReports)
201205
// Loop through vhostopts we processed and have a status for
206+
var multierr *multierror.Error
202207
for vhOptKey, status := range p.classicStatusCache {
203208
// get the obj by namespacedName
204-
vhOptObj, _ := p.vhOptionClient.Read(vhOptKey.Namespace, vhOptKey.Name, clients.ReadOpts{Ctx: ctx})
209+
vhOptObj, err := p.vhOptionClient.Read(vhOptKey.Namespace, vhOptKey.Name, clients.ReadOpts{Ctx: ctx})
210+
if err != nil {
211+
multierr = multierror.Append(multierr, eris.Wrapf(err, "%s %s in namespace %s", ReadingVirtualHostOptionErrStr, vhOptKey.Name, vhOptKey.Namespace))
212+
continue
213+
}
205214

206215
// mark this object to be processed
207216
virtualHostOptionReport.Accept(vhOptObj)
@@ -214,13 +223,14 @@ func (p *plugin) ApplyStatusPlugin(ctx context.Context, statusCtx *plugins.Statu
214223
virtualHostOptionReport.AddWarnings(vhOptObj, status.warnings...)
215224

216225
// actually write out the reports!
217-
err := p.statusReporter.WriteReports(ctx, virtualHostOptionReport, status.subresourceStatus)
226+
err = p.statusReporter.WriteReports(ctx, virtualHostOptionReport, status.subresourceStatus)
218227
if err != nil {
219-
return eris.Wrap(err, "writing status report from VirtualHostOptionPlugin")
228+
multierr = multierror.Append(multierr, eris.Wrap(err, "writing status report from VirtualHostOptionPlugin"))
229+
continue
220230
}
221231

222232
}
223-
return nil
233+
return multierr.ErrorOrNil()
224234
}
225235

226236
// given a ProxyReport, extract and aggregate all VirtualHost errors that have VirtualHostOption source metadata

‎projects/gateway2/translator/plugins/virtualhostoptions/virtualhost_options_plugin_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import (
1717
"github.com/solo-io/gloo/projects/gateway2/translator/plugins/utils"
1818
vhoptquery "github.com/solo-io/gloo/projects/gateway2/translator/plugins/virtualhostoptions/query"
1919
"github.com/solo-io/gloo/projects/gateway2/translator/testutils"
20+
"github.com/solo-io/gloo/projects/gateway2/translator/translatorutils"
2021
"github.com/solo-io/gloo/projects/gateway2/wellknown"
22+
"github.com/solo-io/gloo/projects/gloo/pkg/api/grpc/validation"
2123
v1 "github.com/solo-io/gloo/projects/gloo/pkg/api/v1"
2224
"github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/headers"
2325
"github.com/solo-io/gloo/projects/gloo/pkg/api/v1/options/retries"
@@ -194,6 +196,27 @@ var _ = Describe("VirtualHostOptions Plugin", func() {
194196
}
195197
})
196198
})
199+
200+
When("There is an error reading the VirtualHostOptions", func() {
201+
It("errors out", func() {
202+
plugin.ApplyListenerPlugin(ctx, listenerCtx, outputListener)
203+
204+
statusCtx := &plugins.StatusContext{
205+
ProxiesWithReports: []translatorutils.ProxyWithReports{
206+
{
207+
Proxy: &v1.Proxy{},
208+
Reports: translatorutils.TranslationReports{
209+
ProxyReport: &validation.ProxyReport{},
210+
ResourceReports: reporter.ResourceReports{},
211+
},
212+
},
213+
},
214+
}
215+
216+
err := plugin.ApplyStatusPlugin(ctx, statusCtx)
217+
Expect(err).To(MatchError(ContainSubstring(ReadingVirtualHostOptionErrStr)))
218+
})
219+
})
197220
})
198221
})
199222

0 commit comments

Comments
 (0)
Please sign in to comment.