diff --git a/internal/ingress/controller/store/store.go b/internal/ingress/controller/store/store.go index 5ec8b733d..b1b4cf131 100644 --- a/internal/ingress/controller/store/store.go +++ b/internal/ingress/controller/store/store.go @@ -615,11 +615,27 @@ func New( }, } + serviceHandler := cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(old, cur interface{}) { + oldSvc := old.(*corev1.Service) + curSvc := cur.(*corev1.Service) + + if reflect.DeepEqual(oldSvc, curSvc) { + return + } + + updateCh.In() <- Event{ + Type: UpdateEvent, + Obj: cur, + } + }, + } + store.informers.Ingress.AddEventHandler(ingEventHandler) store.informers.Endpoint.AddEventHandler(epEventHandler) store.informers.Secret.AddEventHandler(secrEventHandler) store.informers.ConfigMap.AddEventHandler(cmEventHandler) - store.informers.Service.AddEventHandler(cache.ResourceEventHandlerFuncs{}) + store.informers.Service.AddEventHandler(serviceHandler) store.informers.Pod.AddEventHandler(podEventHandler) // do not wait for informers to read the configmap configuration diff --git a/test/e2e/servicebackend/service_externalname.go b/test/e2e/servicebackend/service_externalname.go index 343817a12..bef790c15 100644 --- a/test/e2e/servicebackend/service_externalname.go +++ b/test/e2e/servicebackend/service_externalname.go @@ -17,15 +17,20 @@ limitations under the License. package servicebackend import ( + "context" + "fmt" "net/http" "strings" + "time" "github.com/onsi/ginkgo" + "github.com/stretchr/testify/assert" core "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/ingress-nginx/internal/nginx" "k8s.io/ingress-nginx/test/e2e/framework" ) @@ -203,4 +208,73 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() { Expect(). Status(http.StatusOK) }) + + ginkgo.It("should update the external name after a service update", func() { + host := "echo" + + svc := &core.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: framework.HTTPBinService, + Namespace: f.Namespace, + }, + Spec: corev1.ServiceSpec{ + ExternalName: "httpbin.org", + Type: corev1.ServiceTypeExternalName, + Ports: []corev1.ServicePort{ + { + Name: host, + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: "TCP", + }, + }, + }, + } + f.EnsureService(svc) + + ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBinService, 80, nil) + ing.Spec.Rules[0].HTTP.Paths[0].Backend.ServicePort = intstr.FromString(host) + f.EnsureIngress(ing) + + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, "proxy_pass http://upstream_balancer;") + }) + + body := f.HTTPTestClient(). + GET("/get"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Body(). + Raw() + + assert.Contains(ginkgo.GinkgoT(), body, `"X-Forwarded-Host": "echo"`) + + svc, err := f.KubeClientSet.CoreV1().Services(f.Namespace).Get(context.TODO(), framework.HTTPBinService, metav1.GetOptions{}) + assert.Nil(ginkgo.GinkgoT(), err, "unexpected error obtaining httpbin service") + + svc.Spec.ExternalName = "eu.httpbin.org" + + _, err = f.KubeClientSet.CoreV1().Services(f.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{}) + assert.Nil(ginkgo.GinkgoT(), err, "unexpected error updating httpbin service") + + time.Sleep(5 * time.Second) + + body = f.HTTPTestClient(). + GET("/get"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Body(). + Raw() + + assert.Contains(ginkgo.GinkgoT(), body, `"X-Forwarded-Host": "echo"`) + + ginkgo.By("checking the service is updated to use eu.httpbin.org") + curlCmd := fmt.Sprintf("curl --fail --silent http://localhost:%v/configuration/backends", nginx.StatusPort) + output, err := f.ExecIngressPod(curlCmd) + assert.Nil(ginkgo.GinkgoT(), err) + assert.Contains(ginkgo.GinkgoT(), output, `{"address":"eu.httpbin.org"`) + }) })