Repository: kubernetes-sigs/aws-alb-ingress-controller (go)

Issue:
Ingress controller reconciles all endpoints sequentially causing delay that results in 502s
When a pod is deleted from k8s, ingress controller receives endpoint change event and reconciles all endpoints sequentially. As number of ingress resources are added, the delay for reconciliation with k8s cluster gets longer.  This is very easy to reproduce because endpoints are reconciled in alphabetical order.  Updating endpoint for last service will take the longest.

In our case, we have 8 ingress resources. It takes at least 15 seconds for AWS ingress controller to reconcile with k8s cluster. Once pod has been terminated, this results in `502`. 

The workaround is to add preStop delay to the pod.  We have added 30 seconds delay to resolve this issue. However, this is not scalable as more ingress resources are being added to the cluster.

Controller should only reconcile endpoint being changed to improve efficiency. If not, endpoints could be reconciled in parallel.

There is already a `TODO` for this.
https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/31aa413a6b63ebc4e30b400f94613fda485c0a2b/internal/ingress/controller/handlers/endpoints.go#L52



Pull request description:
Optimize endpoint reconcile
replace #1173 (fix typo in branch name)
closes #1103 #1163

On an endpoint event, check the ingress in the namespace and reconsile only the one that are related to the endpoint.

Patch:
diff --git a/docs/examples/alb-ingress-controller.yaml b/docs/examples/alb-ingress-controller.yaml
index d333f13d9..9bbd5b0ba 100644
--- a/docs/examples/alb-ingress-controller.yaml
+++ b/docs/examples/alb-ingress-controller.yaml
@@ -53,7 +53,7 @@ spec:
             # Maximum number of times to retry the aws calls.
             # defaults to 10.
             # - --aws-max-retries=10
-          env:
+          # env:
             # AWS key id for authenticating with the AWS API.
             # This is only here for examples. It's recommended you instead use
             # a project like kube2iam for granting access.
diff --git a/internal/alb/tg/targetgroup_group.go b/internal/alb/tg/targetgroup_group.go
index 1133b2d14..ad664ead5 100644
--- a/internal/alb/tg/targetgroup_group.go
+++ b/internal/alb/tg/targetgroup_group.go
@@ -5,17 +5,15 @@ import (
 	"fmt"
 
 	"github.com/aws/aws-sdk-go/service/elbv2"
-	"k8s.io/apimachinery/pkg/util/intstr"
-
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/alb/tags"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/albctx"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/aws"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/action"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/backend"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/controller/store"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/k8s"
 	extensions "k8s.io/api/extensions/v1beta1"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/intstr"
 	"k8s.io/apimachinery/pkg/util/sets"
 )
 
@@ -60,7 +58,7 @@ type defaultGroupController struct {
 func (controller *defaultGroupController) Reconcile(ctx context.Context, ingress *extensions.Ingress) (TargetGroupGroup, error) {
 	tgByBackend := make(map[extensions.IngressBackend]TargetGroup)
 
-	backends, err := controller.extractTargetGroupBackends(ingress)
+	backends, err := ExtractTargetGroupBackends(ingress)
 	if err != nil {
 		return TargetGroupGroup{}, err
 	}
@@ -112,7 +110,7 @@ func (controller *defaultGroupController) Delete(ctx context.Context, ingressKey
 	return controller.GC(ctx, tgGroup)
 }
 
-func (controller *defaultGroupController) extractTargetGroupBackends(ingress *extensions.Ingress) ([]extensions.IngressBackend, error) {
+func ExtractTargetGroupBackends(ingress *extensions.Ingress) ([]extensions.IngressBackend, error) {
 	var rawIngBackends []extensions.IngressBackend
 	if ingress.Spec.Backend != nil {
 		rawIngBackends = append(rawIngBackends, *ingress.Spec.Backend)
@@ -134,12 +132,14 @@ func (controller *defaultGroupController) extractTargetGroupBackends(ingress *ex
 		ingBackends = append(ingBackends, ingBackend)
 	}
 
-	ingAnnos, err := controller.store.GetIngressAnnotations(k8s.MetaNamespaceKey(ingress))
+	raw, err := action.NewParser().Parse(ingress)
 	if err != nil {
 		return nil, err
 	}
 
-	for _, action := range ingAnnos.Action.Actions {
+	actions := raw.(*action.Config).Actions
+
+	for _, action := range actions {
 		if aws.StringValue(action.Type) != elbv2.ActionTypeEnumForward {
 			continue
 		}
diff --git a/internal/ingress/annotations/action/main.go b/internal/ingress/annotations/action/main.go
index 68199245e..bcafc11ee 100644
--- a/internal/ingress/annotations/action/main.go
+++ b/internal/ingress/annotations/action/main.go
@@ -10,7 +10,6 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/elbv2"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/parser"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/resolver"
 	"k8s.io/apimachinery/pkg/util/intstr"
 )
 
@@ -21,13 +20,11 @@ type Config struct {
 	Actions map[string]Action
 }
 
-type actionParser struct {
-	r resolver.Resolver
-}
+type actionParser struct{}
 
 // NewParser creates a new target group annotation parser
-func NewParser(r resolver.Resolver) parser.IngressAnnotation {
-	return &actionParser{r}
+func NewParser() parser.IngressAnnotation {
+	return &actionParser{}
 }
 
 // Parse parses the annotations contained in the resource
diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go
index cd9d177fd..5b86f3a24 100644
--- a/internal/ingress/annotations/annotations.go
+++ b/internal/ingress/annotations/annotations.go
@@ -99,7 +99,7 @@ type Extractor struct {
 func NewIngressAnnotationExtractor(cfg resolver.Resolver) Extractor {
 	return Extractor{
 		map[string]parser.IngressAnnotation{
-			"Action":       action.NewParser(cfg),
+			"Action":       action.NewParser(),
 			"Conditions":   conditions.NewParser(),
 			"HealthCheck":  healthcheck.NewParser(cfg),
 			"TargetGroup":  targetgroup.NewParser(cfg),
diff --git a/internal/ingress/controller/handlers/endpoints.go b/internal/ingress/controller/handlers/endpoints.go
index a4af4b313..d91645ded 100644
--- a/internal/ingress/controller/handlers/endpoints.go
+++ b/internal/ingress/controller/handlers/endpoints.go
@@ -5,6 +5,7 @@ import (
 	"reflect"
 
 	"github.com/golang/glog"
+	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/alb/tg"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/class"
 	corev1 "k8s.io/api/core/v1"
 	extensions "k8s.io/api/extensions/v1beta1"
@@ -48,7 +49,7 @@ func (h *EnqueueRequestsForEndpointsEvent) Delete(e event.DeleteEvent, queue wor
 func (h *EnqueueRequestsForEndpointsEvent) Generic(event.GenericEvent, workqueue.RateLimitingInterface) {
 }
 
-//TODO: this can be further optimized to only included ingresses referenced this endpoints(service) :D
+//TODO: this can be further optimized to only reconcile the target group referenced by the endpoints(service) :D
 func (h *EnqueueRequestsForEndpointsEvent) enqueueImpactedIngresses(endpoints *corev1.Endpoints, queue workqueue.RateLimitingInterface) {
 	ingressList := &extensions.IngressList{}
 	if err := h.Cache.List(context.Background(), client.InNamespace(endpoints.Namespace), ingressList); err != nil {
@@ -60,11 +61,29 @@ func (h *EnqueueRequestsForEndpointsEvent) enqueueImpactedIngresses(endpoints *c
 		if !class.IsValidIngress(h.IngressClass, &ingress) {
 			continue
 		}
-		queue.Add(reconcile.Request{
-			NamespacedName: types.NamespacedName{
-				Namespace: ingress.Namespace,
-				Name:      ingress.Name,
-			},
-		})
+
+		backends, err := tg.ExtractTargetGroupBackends(&ingress)
+		if err != nil {
+			glog.Errorf("Failed to extract backend services from ingress: %v, reconcile the ingress. error: %e", ingress.Name, err)
+			queue.Add(reconcile.Request{
+				NamespacedName: types.NamespacedName{
+					Namespace: ingress.Namespace,
+					Name:      ingress.Name,
+				},
+			})
+			break
+		}
+
+		for _, backend := range backends {
+			if backend.ServiceName == endpoints.Name {
+				queue.Add(reconcile.Request{
+					NamespacedName: types.NamespacedName{
+						Namespace: ingress.Namespace,
+						Name:      ingress.Name,
+					},
+				})
+				break
+			}
+		}
 	}
 }
diff --git a/mocks/RateLimitingInterface.go b/mocks/RateLimitingInterface.go
new file mode 100644
index 000000000..1dc671cee
--- /dev/null
+++ b/mocks/RateLimitingInterface.go
@@ -0,0 +1,109 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	time "time"
+
+	mock "github.com/stretchr/testify/mock"
+)
+
+// RateLimitingInterface is an autogenerated mock type for the RateLimitingInterface type
+type RateLimitingInterface struct {
+	mock.Mock
+}
+
+// Add provides a mock function with given fields: item
+func (_m *RateLimitingInterface) Add(item interface{}) {
+	_m.Called(item)
+}
+
+// AddAfter provides a mock function with given fields: item, duration
+func (_m *RateLimitingInterface) AddAfter(item interface{}, duration time.Duration) {
+	_m.Called(item, duration)
+}
+
+// AddRateLimited provides a mock function with given fields: item
+func (_m *RateLimitingInterface) AddRateLimited(item interface{}) {
+	_m.Called(item)
+}
+
+// Done provides a mock function with given fields: item
+func (_m *RateLimitingInterface) Done(item interface{}) {
+	_m.Called(item)
+}
+
+// Forget provides a mock function with given fields: item
+func (_m *RateLimitingInterface) Forget(item interface{}) {
+	_m.Called(item)
+}
+
+// Get provides a mock function with given fields:
+func (_m *RateLimitingInterface) Get() (interface{}, bool) {
+	ret := _m.Called()
+
+	var r0 interface{}
+	if rf, ok := ret.Get(0).(func() interface{}); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(interface{})
+		}
+	}
+
+	var r1 bool
+	if rf, ok := ret.Get(1).(func() bool); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Get(1).(bool)
+	}
+
+	return r0, r1
+}
+
+// Len provides a mock function with given fields:
+func (_m *RateLimitingInterface) Len() int {
+	ret := _m.Called()
+
+	var r0 int
+	if rf, ok := ret.Get(0).(func() int); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	return r0
+}
+
+// NumRequeues provides a mock function with given fields: item
+func (_m *RateLimitingInterface) NumRequeues(item interface{}) int {
+	ret := _m.Called(item)
+
+	var r0 int
+	if rf, ok := ret.Get(0).(func(interface{}) int); ok {
+		r0 = rf(item)
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	return r0
+}
+
+// ShutDown provides a mock function with given fields:
+func (_m *RateLimitingInterface) ShutDown() {
+	_m.Called()
+}
+
+// ShuttingDown provides a mock function with given fields:
+func (_m *RateLimitingInterface) ShuttingDown() bool {
+	ret := _m.Called()
+
+	var r0 bool
+	if rf, ok := ret.Get(0).(func() bool); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	return r0
+}


Test patch:
diff --git a/internal/alb/tg/targetgroup_group_test.go b/internal/alb/tg/targetgroup_group_test.go
index 1da7ee364..c67e04faf 100644
--- a/internal/alb/tg/targetgroup_group_test.go
+++ b/internal/alb/tg/targetgroup_group_test.go
@@ -6,12 +6,9 @@ import (
 	"testing"
 
 	"github.com/aws/aws-sdk-go/service/elbv2"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/action"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/conditions"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/controller/store"
-
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/aws"
+	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/controller/config"
+	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/controller/store"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/mocks"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -40,21 +37,14 @@ type DeleteTargetGroupByArnCall struct {
 	Err error
 }
 
-type StoreGetIngressAnnotationsCall struct {
-	IngressKey   string
-	IngressAnnos *annotations.Ingress
-	Err          error
-}
-
 func TestDefaultGroupController_Reconcile(t *testing.T) {
 	for _, tc := range []struct {
-		Name                           string
-		Ingress                        extensions.Ingress
-		TGReconcileCalls               []TGReconcileCall
-		TagTGGroupCall                 *TagTGGroupCall
-		StoreGetIngressAnnotationsCall *StoreGetIngressAnnotationsCall
-		ExpectedTGGroup                TargetGroupGroup
-		ExpectedError                  error
+		Name             string
+		Ingress          extensions.Ingress
+		TGReconcileCalls []TGReconcileCall
+		TagTGGroupCall   *TagTGGroupCall
+		ExpectedTGGroup  TargetGroupGroup
+		ExpectedError    error
 	}{
 		{
 			Name: "Reconcile succeeds with duplicated targetGroup",
@@ -148,17 +138,6 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 				IngressName: "ingress",
 				Tags:        map[string]string{"key1": "value1", "key2": "value2"},
 			},
-			StoreGetIngressAnnotationsCall: &StoreGetIngressAnnotationsCall{
-				IngressKey: "namespace/ingress",
-				IngressAnnos: &annotations.Ingress{
-					Action: &action.Config{
-						Actions: nil,
-					},
-					Conditions: &conditions.Config{
-						Conditions: nil,
-					},
-				},
-			},
 			ExpectedTGGroup: TargetGroupGroup{
 				TGByBackend: map[extensions.IngressBackend]TargetGroup{
 					{
@@ -240,17 +219,6 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 				IngressName: "ingress",
 				Tags:        map[string]string{"key1": "value1", "key2": "value2"},
 			},
-			StoreGetIngressAnnotationsCall: &StoreGetIngressAnnotationsCall{
-				IngressKey: "namespace/ingress",
-				IngressAnnos: &annotations.Ingress{
-					Action: &action.Config{
-						Actions: nil,
-					},
-					Conditions: &conditions.Config{
-						Conditions: nil,
-					},
-				},
-			},
 			ExpectedTGGroup: TargetGroupGroup{
 				TGByBackend: map[extensions.IngressBackend]TargetGroup{
 					{
@@ -337,17 +305,6 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 				IngressName: "ingress",
 				Tags:        map[string]string{"key1": "value1", "key2": "value2"},
 			},
-			StoreGetIngressAnnotationsCall: &StoreGetIngressAnnotationsCall{
-				IngressKey: "namespace/ingress",
-				IngressAnnos: &annotations.Ingress{
-					Action: &action.Config{
-						Actions: nil,
-					},
-					Conditions: &conditions.Config{
-						Conditions: nil,
-					},
-				},
-			},
 			ExpectedTGGroup: TargetGroupGroup{
 				TGByBackend: map[extensions.IngressBackend]TargetGroup{
 					{
@@ -370,8 +327,9 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 			Name: "Reconcile succeeds with backend using annotation",
 			Ingress: extensions.Ingress{
 				ObjectMeta: metav1.ObjectMeta{
-					Name:      "ingress",
-					Namespace: "namespace",
+					Name:        "ingress",
+					Namespace:   "namespace",
+					Annotations: map[string]string{},
 				},
 				Spec: extensions.IngressSpec{
 					Rules: []extensions.IngressRule{
@@ -416,17 +374,6 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 				IngressName: "ingress",
 				Tags:        map[string]string{"key1": "value1", "key2": "value2"},
 			},
-			StoreGetIngressAnnotationsCall: &StoreGetIngressAnnotationsCall{
-				IngressKey: "namespace/ingress",
-				IngressAnnos: &annotations.Ingress{
-					Action: &action.Config{
-						Actions: nil,
-					},
-					Conditions: &conditions.Config{
-						Conditions: nil,
-					},
-				},
-			},
 			ExpectedTGGroup: TargetGroupGroup{
 				TGByBackend: map[extensions.IngressBackend]TargetGroup{
 					{
@@ -443,6 +390,9 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "ingress",
 					Namespace: "namespace",
+					Annotations: map[string]string{
+						"alb.ingress.kubernetes.io/actions.weighted-routing": `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"Weight":1,"ServiceName":"service1","ServicePort":"80"},{"Weight":1,"ServiceName":"service2","ServicePort":"80"}]}}`,
+					},
 				},
 				Spec: extensions.IngressSpec{
 					Rules: []extensions.IngressRule{
@@ -496,35 +446,6 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 				IngressName: "ingress",
 				Tags:        map[string]string{"key1": "value1", "key2": "value2"},
 			},
-			StoreGetIngressAnnotationsCall: &StoreGetIngressAnnotationsCall{
-				IngressKey: "namespace/ingress",
-				IngressAnnos: &annotations.Ingress{
-					Action: &action.Config{
-						Actions: map[string]action.Action{
-							"weighted-routing": {
-								Type: aws.String(elbv2.ActionTypeEnumForward),
-								ForwardConfig: &action.ForwardActionConfig{
-									TargetGroups: []*action.TargetGroupTuple{
-										{
-											ServiceName: aws.String("service1"),
-											ServicePort: aws.String("80"),
-											Weight:      aws.Int64(1),
-										},
-										{
-											ServiceName: aws.String("service2"),
-											ServicePort: aws.String("80"),
-											Weight:      aws.Int64(1),
-										},
-									},
-								},
-							},
-						},
-					},
-					Conditions: &conditions.Config{
-						Conditions: nil,
-					},
-				},
-			},
 			ExpectedTGGroup: TargetGroupGroup{
 				TGByBackend: map[extensions.IngressBackend]TargetGroup{
 					{
@@ -575,17 +496,6 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 					Err: errors.New("TGReconcileCall"),
 				},
 			},
-			StoreGetIngressAnnotationsCall: &StoreGetIngressAnnotationsCall{
-				IngressKey: "namespace/ingress",
-				IngressAnnos: &annotations.Ingress{
-					Action: &action.Config{
-						Actions: nil,
-					},
-					Conditions: &conditions.Config{
-						Conditions: nil,
-					},
-				},
-			},
 			ExpectedError: errors.New("TGReconcileCall"),
 		},
 	} {
@@ -602,10 +512,10 @@ func TestDefaultGroupController_Reconcile(t *testing.T) {
 			}
 
 			mockStore := &store.MockStorer{}
-			if tc.StoreGetIngressAnnotationsCall != nil {
-				mockStore.On("GetIngressAnnotations", tc.StoreGetIngressAnnotationsCall.IngressKey).Return(
-					tc.StoreGetIngressAnnotationsCall.IngressAnnos, tc.StoreGetIngressAnnotationsCall.Err)
-			}
+			mockStore.On("GetConfig").Return(
+				&config.Configuration{
+					DefaultTargetType: elbv2.TargetTypeEnumInstance,
+				}, nil)
 
 			controller := &defaultGroupController{
 				cloud:        cloud,
diff --git a/internal/ingress/annotations/action/main_test.go b/internal/ingress/annotations/action/main_test.go
index 4c6b85f27..fc67d6b37 100644
--- a/internal/ingress/annotations/action/main_test.go
+++ b/internal/ingress/annotations/action/main_test.go
@@ -9,13 +9,8 @@ import (
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/aws"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/annotations/parser"
 	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/controller/dummy"
-	"github.com/kubernetes-sigs/aws-alb-ingress-controller/internal/ingress/resolver"
 )
 
-type mockBackend struct {
-	resolver.Mock
-}
-
 func TestIngressActions(t *testing.T) {
 	tcs := []struct {
 		name           string
@@ -99,7 +94,7 @@ func TestIngressActions(t *testing.T) {
 	ing := dummy.NewIngress()
 	ing.SetAnnotations(data)
 
-	actionsConfigRaw, err := NewParser(mockBackend{}).Parse(ing)
+	actionsConfigRaw, err := NewParser().Parse(ing)
 	if err != nil {
 		t.Error(err)
 		return
@@ -151,7 +146,7 @@ func TestInvalidIngressActions(t *testing.T) {
 			data := map[string]string{}
 			data[parser.GetAnnotationWithPrefix("actions.test-action")] = tc.actionJSON
 			ing.SetAnnotations(data)
-			_, err := NewParser(mockBackend{}).Parse(ing)
+			_, err := NewParser().Parse(ing)
 			assert.EqualError(t, err, tc.expectedErr)
 		})
 	}
diff --git a/internal/ingress/controller/handlers/endpoints_test.go b/internal/ingress/controller/handlers/endpoints_test.go
new file mode 100644
index 000000000..698d88224
--- /dev/null
+++ b/internal/ingress/controller/handlers/endpoints_test.go
@@ -0,0 +1,143 @@
+package handlers
+
+import (
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	"github.com/kubernetes-sigs/aws-alb-ingress-controller/mocks"
+	mock_cache "github.com/kubernetes-sigs/aws-alb-ingress-controller/mocks/controller-runtime/cache"
+	corev1 "k8s.io/api/core/v1"
+	extensions "k8s.io/api/extensions/v1beta1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+)
+
+func TestEnqueueImpactedIngresses(t *testing.T) {
+	const namespace = "namespace"
+	const service = "service"
+	IngressList := extensions.IngressList{
+		Items: []extensions.Ingress{
+			{
+				TypeMeta: v1.TypeMeta{},
+				ObjectMeta: v1.ObjectMeta{
+					Name:      "relevant-ingress",
+					Namespace: namespace,
+				},
+				Spec: extensions.IngressSpec{
+					Rules: []extensions.IngressRule{
+						{
+							Host: "d1.example.com",
+							IngressRuleValue: extensions.IngressRuleValue{
+								HTTP: &extensions.HTTPIngressRuleValue{
+									Paths: []extensions.HTTPIngressPath{
+										{
+											Path: "/path1",
+											Backend: extensions.IngressBackend{
+												ServiceName: service,
+												ServicePort: intstr.FromInt(80),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			{
+				TypeMeta: v1.TypeMeta{},
+				ObjectMeta: v1.ObjectMeta{
+					Name:      "relevant-ingress-with-annotation",
+					Namespace: namespace,
+					Annotations: map[string]string{
+						"alb.ingress.kubernetes.io/actions.weighted-routing": `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"Weight":1,"ServiceName":"service","ServicePort":"80"},{"Weight":1,"ServiceName":"service2","ServicePort":"80"}]}}`,
+					},
+				},
+				Spec: extensions.IngressSpec{
+					Rules: []extensions.IngressRule{
+						{
+							Host: "d1.example.com",
+							IngressRuleValue: extensions.IngressRuleValue{
+								HTTP: &extensions.HTTPIngressRuleValue{
+									Paths: []extensions.HTTPIngressPath{
+										{
+											Path: "/path1",
+											Backend: extensions.IngressBackend{
+												ServiceName: "weighted-routing",
+												ServicePort: intstr.FromString("use-annotation"),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			{
+				TypeMeta: v1.TypeMeta{},
+				ObjectMeta: v1.ObjectMeta{
+					Name:      "not-relevant-ingress-different-service",
+					Namespace: namespace,
+				},
+				Spec: extensions.IngressSpec{
+					Rules: []extensions.IngressRule{
+						{
+							Host: "d1.example.com",
+							IngressRuleValue: extensions.IngressRuleValue{
+								HTTP: &extensions.HTTPIngressRuleValue{
+									Paths: []extensions.HTTPIngressPath{
+										{
+											Path: "/path1",
+											Backend: extensions.IngressBackend{
+												ServiceName: "service1",
+												ServicePort: intstr.FromInt(80),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
+	mockCache := mock_cache.NewMockCache(ctrl)
+	mockCache.EXPECT().List(gomock.Any(), client.InNamespace(namespace), &extensions.IngressList{}).SetArg(2, IngressList)
+
+	handler := EnqueueRequestsForEndpointsEvent{
+		Cache: mockCache,
+	}
+
+	queueMock := &mocks.RateLimitingInterface{}
+
+	queueMock.On("Add", reconcile.Request{
+		NamespacedName: types.NamespacedName{
+			Namespace: namespace,
+			Name:      "relevant-ingress",
+		},
+	})
+
+	queueMock.On("Add", reconcile.Request{
+		NamespacedName: types.NamespacedName{
+			Namespace: namespace,
+			Name:      "relevant-ingress-with-annotation",
+		},
+	})
+
+	handler.enqueueImpactedIngresses(&corev1.Endpoints{
+		ObjectMeta: v1.ObjectMeta{
+			Name:      service,
+			Namespace: namespace,
+		},
+	},
+		queueMock)
+}