Skip to content

Commit 606a395

Browse files
Fixed target group leaking problems (#60)
* Fixed the target group leaking issue * Fixed target_group_manager unit test error * Fixed AWS resource tag to determine if a target group is stale * Add unit test * Update tg synthesizer unit test to align with current logic * Update the logging * Add more unit tests to cover serviceexport * Make sure retry deleting stale target group when its targets are in draining state * make sure stale rules get deleted during deleting stale target groups * Reconcile every 10 min * Address code review comments
1 parent fb10944 commit 606a395

File tree

6 files changed

+399
-194
lines changed

6 files changed

+399
-194
lines changed

pkg/deploy/lattice/target_group_manager.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
type TargetGroupManager interface {
1717
Create(ctx context.Context, targetGroup *latticemodel.TargetGroup) (latticemodel.TargetGroupStatus, error)
1818
Delete(ctx context.Context, targetGroup *latticemodel.TargetGroup) error
19-
List(ctx context.Context) ([]vpclattice.GetTargetGroupOutput, error)
19+
List(ctx context.Context) ([]targetGroupOutput, error)
2020
Get(tx context.Context, targetGroup *latticemodel.TargetGroup) (latticemodel.TargetGroupStatus, error)
2121
}
2222

@@ -189,9 +189,14 @@ func (s *defaultTargetGroupManager) Delete(ctx context.Context, targetGroup *lat
189189
return err
190190
}
191191

192-
func (s *defaultTargetGroupManager) List(ctx context.Context) ([]vpclattice.GetTargetGroupOutput, error) {
192+
type targetGroupOutput struct {
193+
getTargetGroupOutput vpclattice.GetTargetGroupOutput
194+
targetGroupTags *vpclattice.ListTagsForResourceOutput
195+
}
196+
197+
func (s *defaultTargetGroupManager) List(ctx context.Context) ([]targetGroupOutput, error) {
193198
vpcLatticeSess := s.cloud.Lattice()
194-
var tgList []vpclattice.GetTargetGroupOutput
199+
var tgList []targetGroupOutput
195200
targetGroupListInput := vpclattice.ListTargetGroupsInput{}
196201
resp, err := vpcLatticeSess.ListTargetGroupsAsList(ctx, &targetGroupListInput)
197202
glog.V(6).Infof("ManagerList: %v, err: %v \n", resp, err)
@@ -213,7 +218,25 @@ func (s *defaultTargetGroupManager) List(ctx context.Context) ([]vpclattice.GetT
213218
//glog.V(6).Infof("Manager-List: tg-vpc %v , config.vpc %v\n", aws.StringValue(tgOutput.Config.VpcId), config.VpcID)
214219

215220
if tgOutput.Config != nil && aws.StringValue(tgOutput.Config.VpcIdentifier) == config.VpcID {
216-
tgList = append(tgList, *tgOutput)
221+
// retrieve target group tags
222+
//ListTagsForResourceWithContext
223+
tagsInput := vpclattice.ListTagsForResourceInput{
224+
ResourceArn: tg.Arn,
225+
}
226+
227+
tagsOutput, err := vpcLatticeSess.ListTagsForResourceWithContext(ctx, &tagsInput)
228+
229+
glog.V(6).Infof("tagsOutput %v, err: %v", tagsOutput, err)
230+
231+
if err != nil {
232+
// setting it to nil, so the caller knows there is tag resource associated to this target group
233+
tagsOutput = nil
234+
}
235+
tgOutput := targetGroupOutput{
236+
getTargetGroupOutput: *tgOutput,
237+
targetGroupTags: tagsOutput,
238+
}
239+
tgList = append(tgList, tgOutput)
217240
}
218241
}
219242
return tgList, err

pkg/deploy/lattice/target_group_manager_mock.go

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/deploy/lattice/target_group_manager_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,12 +813,20 @@ func Test_ListTG_TGsExist(t *testing.T) {
813813
mockCloud := mocks_aws.NewMockCloud(c)
814814
mockVpcLatticeSess.EXPECT().ListTargetGroupsAsList(ctx, gomock.Any()).Return(listTGOutput, nil)
815815
mockVpcLatticeSess.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(getTG1, nil)
816+
// assume no tags
817+
mockVpcLatticeSess.EXPECT().ListTagsForResourceWithContext(ctx, gomock.Any()).Return(nil, errors.New("no tags"))
816818
mockVpcLatticeSess.EXPECT().GetTargetGroupWithContext(ctx, gomock.Any()).Return(getTG2, nil)
819+
817820
mockCloud.EXPECT().Lattice().Return(mockVpcLatticeSess)
818821

819822
tgManager := NewTargetGroupManager(mockCloud)
820823
tgList, err := tgManager.List(ctx)
821-
expect := []vpclattice.GetTargetGroupOutput{*getTG1}
824+
expect := []targetGroupOutput{
825+
{
826+
getTargetGroupOutput: *getTG1,
827+
targetGroupTags: nil,
828+
},
829+
}
822830

823831
assert.Nil(t, err)
824832
assert.Equal(t, tgList, expect)
@@ -837,9 +845,10 @@ func Test_ListTG_NoTG(t *testing.T) {
837845

838846
tgManager := NewTargetGroupManager(mockCloud)
839847
tgList, err := tgManager.List(ctx)
848+
expectTgList := []targetGroupOutput(nil)
840849

841850
assert.Nil(t, err)
842-
assert.Equal(t, tgList, []vpclattice.GetTargetGroupOutput(nil))
851+
assert.Equal(t, tgList, expectTgList)
843852
}
844853

845854
func Test_Get(t *testing.T) {

pkg/deploy/lattice/target_group_synthesizer.go

Lines changed: 132 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"github.com/golang/glog"
77
"strings"
88

9+
"k8s.io/apimachinery/pkg/types"
910
"sigs.k8s.io/controller-runtime/pkg/client"
1011
"sigs.k8s.io/gateway-api/apis/v1alpha2"
12+
mcs_api "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
1113

1214
lattice_aws "github.com/aws/aws-application-networking-k8s/pkg/aws"
1315
"github.com/aws/aws-application-networking-k8s/pkg/config"
@@ -45,10 +47,10 @@ func (t *targetGroupSynthesizer) Synthesize(ctx context.Context) error {
4547

4648
/* TODO, resolve bug that this might delete other HTTPRoute's TG before they have chance
4749
* to be reconcile during controller restart
50+
*/
4851
if err := t.SynthesizeSDKTargetGroups(ctx); err != nil {
4952
ret = LATTICE_RETRY
5053
}
51-
*/
5254

5355
if ret != "" {
5456
return errors.New(ret)
@@ -158,48 +160,140 @@ func (t *targetGroupSynthesizer) SynthesizeSDKTargetGroups(ctx context.Context)
158160
sdkTGs, err := t.targetGroupManager.List(ctx)
159161

160162
if err != nil {
161-
glog.V(6).Infof("SynthesizeSDKTargetGroups: failed to retrieve sdk TGs %v\n", err)
163+
glog.V(2).Infof("SynthesizeSDKTargetGroups: failed to retrieve sdk TGs %v\n", err)
162164
return nil
163165
}
164166

165167
glog.V(6).Infof("SynthesizeSDKTargetGroups: here is sdkTGs %v len %v \n", sdkTGs, len(sdkTGs))
166168

167169
for _, sdkTG := range sdkTGs {
168170

169-
if *sdkTG.Config.VpcIdentifier != config.VpcID {
170-
glog.V(6).Infof("Ignore target group for other VPCs, other :%v, current vpc %v\n", *sdkTG.Config.VpcIdentifier, config.VpcID)
171+
if *sdkTG.getTargetGroupOutput.Config.VpcIdentifier != config.VpcID {
172+
glog.V(6).Infof("Ignore target group ARN %v Name %v for other VPCs",
173+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
171174
continue
172175
}
173-
if tg, err := t.latticeDataStore.GetTargetGroup(*sdkTG.Name, true); err == nil {
174-
glog.V(6).Infof("Ignore target group created by service import %v\n", tg)
176+
177+
// does target group have K8S tags, ignore if it is not tagged
178+
tgTags := sdkTG.targetGroupTags
179+
180+
if tgTags == nil || tgTags.Tags == nil {
181+
glog.V(6).Infof("Ignore target group not tagged for K8S, %v, %v \n",
182+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
175183
continue
176184
}
177185

178-
tg, err := t.latticeDataStore.GetTargetGroup(*sdkTG.Name, false)
179-
if err != nil {
180-
staleSDKTGs = append(staleSDKTGs, latticemodel.TargetGroup{
181-
Spec: latticemodel.TargetGroupSpec{
182-
Name: *sdkTG.Name,
183-
LatticeID: *sdkTG.Id,
184-
},
185-
})
186-
} else {
187-
if tg.ByServiceExport {
186+
parentRef, ok := tgTags.Tags[latticemodel.K8SParentRefTypeKey]
187+
if !ok || parentRef == nil {
188+
glog.V(6).Infof("Ignore target group that have no K8S parentRef tag :%v, %v \n",
189+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
190+
continue
191+
}
192+
193+
srvName, ok := tgTags.Tags[latticemodel.K8SServiceNameKey]
194+
195+
if !ok || srvName == nil {
196+
glog.V(6).Infof("Ignore TargetGroup have no servicename tag: %v, %v",
197+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
198+
continue
199+
}
200+
201+
srvNamespace, ok := tgTags.Tags[latticemodel.K8SServiceNamespaceKey]
202+
203+
if !ok || srvNamespace == nil {
204+
glog.V(6).Infof("Ignore TargetGroup have no servicenamespace tag: %v, %v",
205+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
206+
continue
207+
}
208+
209+
// if its parentref is service export, check the parent service export exist
210+
// Ignore if service export exists
211+
if *parentRef == latticemodel.K8SServiceExportType {
212+
glog.V(6).Infof("TargetGroup %v, %v is referenced by ServiceExport",
213+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
214+
215+
glog.V(6).Infof("Determine serviceexport name=%v, namespace=%v exists for targetGroup %v",
216+
*srvName, *srvNamespace, *sdkTG.getTargetGroupOutput.Arn)
217+
218+
srvExportName := types.NamespacedName{
219+
Namespace: *srvNamespace,
220+
Name: *srvName,
221+
}
222+
srvExport := &mcs_api.ServiceExport{}
223+
if err := t.client.Get(ctx, srvExportName, srvExport); err == nil {
224+
225+
glog.V(6).Infof("Ignore TargetGroup(triggered by serviceexport) %v, %v since serviceexport object is found",
226+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
188227
continue
189228
}
229+
}
230+
231+
// if its parentref is HTTP/route, check the parent HTTPRoute exist
232+
// Ignore if httpRoute does NOT exist
233+
if *parentRef == latticemodel.K8SHTTPRouteType {
234+
glog.V(6).Infof("TargetGroup %v, %v is referenced by HTTPRoute",
235+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
190236

191-
if t.isTargetGroupUsedByHTTPRoute(ctx, *sdkTG.Name) {
237+
httpName, ok := tgTags.Tags[latticemodel.K8SHTTPRouteNameKey]
238+
239+
if !ok || httpName == nil {
240+
glog.V(6).Infof("Ignore TargetGroup(triggered by httpRoute) %v, %v have no httproute name tag",
241+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
192242
continue
193243
}
194-
staleSDKTGs = append(staleSDKTGs, latticemodel.TargetGroup{
195-
Spec: latticemodel.TargetGroupSpec{
196-
Name: *sdkTG.Name,
197-
LatticeID: *sdkTG.Id,
198-
},
199-
})
200244

245+
httpNamespace, ok := tgTags.Tags[latticemodel.K8SHTTPRouteNamespaceKey]
246+
247+
if !ok || httpNamespace == nil {
248+
glog.V(6).Infof("Ignore TargetGroup(triggered by httpRoute) %v, %v have no httproute namespace tag",
249+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
250+
continue
251+
}
252+
253+
httprouteName := types.NamespacedName{
254+
Namespace: *httpNamespace,
255+
Name: *httpName,
256+
}
257+
258+
httpRoute := &v1alpha2.HTTPRoute{}
259+
260+
tgName := latticestore.TargetGroupName(*srvName, *srvNamespace)
261+
262+
if err := t.client.Get(ctx, httprouteName, httpRoute); err != nil {
263+
glog.V(6).Infof("tgname %v is not used by httproute %v\n", tgName, httpRoute)
264+
265+
} else {
266+
267+
isUsed := t.isTargetGroupUsedByaHTTPRoute(ctx, tgName, httpRoute)
268+
269+
if isUsed {
270+
271+
glog.V(6).Infof("Ignore TargetGroup(triggered by HTTProute) %v, %v since httproute object is found",
272+
*sdkTG.getTargetGroupOutput.Arn, *sdkTG.getTargetGroupOutput.Name)
273+
274+
continue
275+
} else {
276+
glog.V(6).Infof("tgname %v is not used by httproute %v\n", tgName, httpRoute)
277+
}
278+
}
279+
280+
}
281+
282+
if tg, err := t.latticeDataStore.GetTargetGroup(*sdkTG.getTargetGroupOutput.Name, true); err == nil {
283+
glog.V(6).Infof("Ignore target group created by service import %v\n", tg)
284+
continue
201285
}
202286

287+
glog.V(2).Infof("Append stale SDK TG to stale list Name %v, ARN %v",
288+
*sdkTG.getTargetGroupOutput.Name, *sdkTG.getTargetGroupOutput.Id)
289+
290+
staleSDKTGs = append(staleSDKTGs, latticemodel.TargetGroup{
291+
Spec: latticemodel.TargetGroupSpec{
292+
Name: *sdkTG.getTargetGroupOutput.Name,
293+
LatticeID: *sdkTG.getTargetGroupOutput.Id,
294+
},
295+
})
296+
203297
}
204298

205299
glog.V(6).Infof("SynthesizeSDKTargetGroups, here is the stale target groups list %v stalelen %d\n", staleSDKTGs, len(staleSDKTGs))
@@ -211,8 +305,7 @@ func (t *targetGroupSynthesizer) SynthesizeSDKTargetGroups(ctx context.Context)
211305
err := t.targetGroupManager.Delete(ctx, &sdkTG)
212306
glog.V(6).Infof("SynthesizeSDKTargetGroups, deleting stale target group %v \n", err)
213307

214-
// TODO find out the error code
215-
if err != nil && !strings.Contains(err.Error(), "ConflictException") {
308+
if err != nil && !strings.Contains(err.Error(), "TargetGroup is referenced in routing configuration, listeners or rules of service.") {
216309
ret_err = true
217310
}
218311
// continue on even when there is an err
@@ -227,35 +320,24 @@ func (t *targetGroupSynthesizer) SynthesizeSDKTargetGroups(ctx context.Context)
227320

228321
}
229322

230-
// TODO put following routine to common library
231-
func (t *targetGroupSynthesizer) isTargetGroupUsedByHTTPRoute(ctx context.Context, tgName string) bool {
232-
233-
httpRouteList := &v1alpha2.HTTPRouteList{}
234-
235-
t.client.List(ctx, httpRouteList)
236-
237-
//glog.V(6).Infof("isTargetGroupUsedByHTTPRoute: tgName %v-- %v\n", tgName, httpRouteList)
238-
239-
for _, httpRoute := range httpRouteList.Items {
240-
for _, httpRule := range httpRoute.Spec.Rules {
241-
for _, httpBackendRef := range httpRule.BackendRefs {
242-
//glog.V(6).Infof("isTargetGroupUsedByHTTPRoute: httpBackendRef: %v \n", httpBackendRef)
243-
if string(*httpBackendRef.BackendObjectReference.Kind) != "Service" {
244-
continue
245-
}
246-
namespace := "default"
323+
func (t *targetGroupSynthesizer) isTargetGroupUsedByaHTTPRoute(ctx context.Context, tgName string, httpRoute *v1alpha2.HTTPRoute) bool {
247324

248-
if httpBackendRef.BackendObjectReference.Namespace != nil {
249-
namespace = string(*httpBackendRef.BackendObjectReference.Namespace)
250-
}
251-
refTGName := latticestore.TargetGroupName(string(httpBackendRef.BackendObjectReference.Name), namespace)
252-
//glog.V(6).Infof("refTGName: %s\n", refTGName)
325+
for _, httpRule := range httpRoute.Spec.Rules {
326+
for _, httpBackendRef := range httpRule.BackendRefs {
327+
if string(*httpBackendRef.BackendObjectReference.Kind) != "Service" {
328+
continue
329+
}
330+
namespace := "default"
253331

254-
if tgName == refTGName {
255-
return true
256-
}
332+
if httpBackendRef.BackendObjectReference.Namespace != nil {
333+
namespace = string(*httpBackendRef.BackendObjectReference.Namespace)
334+
}
335+
refTGName := latticestore.TargetGroupName(string(httpBackendRef.BackendObjectReference.Name), namespace)
257336

337+
if tgName == refTGName {
338+
return true
258339
}
340+
259341
}
260342
}
261343

0 commit comments

Comments
 (0)