Repository: nginx/kubernetes-ingress

Issue:
Service Insight does not work for cross-namespace-configuration
### Version

edge

### What Kubernetes platforms are you running on?

Kind

### Steps to reproduce

1. Deploy NIC with service insight enabled
2. Deploy examples/custom-resources/cross-namespace-configuration
3. Get service insight for cafe.example.com
4. Returns 404

Expected is a list of upstream as in the example at https://docs.nginx.com/nginx-ingress-controller/logging-and-monitoring/service-insight/#available-statistics-and-http-response-codes

Hints from issue comments:
@github-actions: Hi @hafe thanks for reporting! 

 Be sure to check out the [docs](https://docs.nginx.com/nginx-ingress-controller) and the [Contributing Guidelines](https://github.com/nginx/kubernetes-ingress/blob/main/CONTRIBUTING.md) while you wait for a human to take a look at this :slightly_smiling_face:

 Cheers!@AlexFenlon: Hey @hafe ,

Thanks for creating the PR, we are putting this into the prioritized backlog so we will get to it as soon as we can. 

Solution patch:
diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go
index 110ab16d6..d7ebe63c4 100644
--- a/internal/configs/configurator.go
+++ b/internal/configs/configurator.go
@@ -311,21 +311,23 @@ func (cnf *Configurator) AddOrUpdateIngress(ingEx *IngressEx) (Warnings, error)
 	return warnings, nil
 }
 
-// virtualServerForHost takes a hostname and returns a VS for the given hostname.
-func (cnf *Configurator) virtualServerForHost(hostname string) *conf_v1.VirtualServer {
+// virtualServerExForHost takes a hostname and returns a VirtualServerEx for the given hostname.
+func (cnf *Configurator) virtualServerExForHost(hostname string) *VirtualServerEx {
 	for _, vsEx := range cnf.virtualServers {
 		if vsEx.VirtualServer.Spec.Host == hostname {
-			return vsEx.VirtualServer
+			return vsEx
 		}
 	}
 	return nil
 }
 
-// upstreamsForVirtualServer takes VirtualServer and returns a list of associated upstreams.
-func (cnf *Configurator) upstreamsForVirtualServer(vs *conf_v1.VirtualServer) []string {
+// upstreamsForVirtualServer takes a VirtualServerEx and returns a list of associated upstreams.
+func (cnf *Configurator) upstreamsForVirtualServer(vsEx *VirtualServerEx) []string {
 	l := nl.LoggerFromContext(cnf.CfgParams.Context)
+	vs := vsEx.VirtualServer
+	var upstreamNames []string
+
 	nl.Debugf(l, "Get upstreamName for vs: %s", vs.Spec.Host)
-	upstreamNames := make([]string, 0, len(vs.Spec.Upstreams))
 
 	virtualServerUpstreamNamer := NewUpstreamNamerForVirtualServer(vs)
 
@@ -334,6 +336,17 @@ func (cnf *Configurator) upstreamsForVirtualServer(vs *conf_v1.VirtualServer) []
 		nl.Debugf(l, "upstream: %s, upstreamName: %s", u.Name, upstreamName)
 		upstreamNames = append(upstreamNames, upstreamName)
 	}
+
+	for _, vsr := range vsEx.VirtualServerRoutes {
+		upstreamNamer := NewUpstreamNamerForVirtualServerRoute(vs, vsr)
+		for _, u := range vsr.Spec.Upstreams {
+			upstreamName := upstreamNamer.GetNameForUpstream(u.Name)
+			nl.Debugf(l, "upstream: %s, upstreamName: %s", u.Name, upstreamName)
+			upstreamNames = append(upstreamNames, upstreamName)
+
+		}
+	}
+
 	return upstreamNames
 }
 
@@ -341,9 +354,9 @@ func (cnf *Configurator) upstreamsForVirtualServer(vs *conf_v1.VirtualServer) []
 func (cnf *Configurator) UpstreamsForHost(hostname string) []string {
 	l := nl.LoggerFromContext(cnf.CfgParams.Context)
 	nl.Debugf(l, "Get upstream for host: %s", hostname)
-	vs := cnf.virtualServerForHost(hostname)
-	if vs != nil {
-		return cnf.upstreamsForVirtualServer(vs)
+	vsEx := cnf.virtualServerExForHost(hostname)
+	if vsEx != nil {
+		return cnf.upstreamsForVirtualServer(vsEx)
 	}
 	return nil
 }



Test patch:
diff --git a/internal/configs/configurator_test.go b/internal/configs/configurator_test.go
index accaecd20..b9f376392 100644
--- a/internal/configs/configurator_test.go
+++ b/internal/configs/configurator_test.go
@@ -1563,6 +1563,24 @@ func TestUpstreamsForHost_ReturnsNilForNoVirtualServers(t *testing.T) {
 	}
 }
 
+func TestUpstreamsForHost_VirtualServerRoutes(t *testing.T) {
+	t.Parallel()
+
+	tcnf := createTestConfigurator(t)
+	tcnf.virtualServers = map[string]*VirtualServerEx{
+		"vs": validVirtualServerExWithRouteUpstreams,
+	}
+
+	want := []string{
+		"vs_default_cafe-vs_vsr_tea_tea-app_tea",
+		"vs_default_cafe-vs_vsr_coffee_coffee-app_coffee",
+	}
+	got := tcnf.UpstreamsForHost("cafe.example.com")
+	if !cmp.Equal(want, got) {
+		t.Error(cmp.Diff(want, got))
+	}
+}
+
 func TestUpstreamsForHost_DoesNotReturnUpstreamsOnBogusHostname(t *testing.T) {
 	t.Parallel()
 
@@ -1834,6 +1852,55 @@ var (
 		},
 	}
 
+	validVirtualServerExWithRouteUpstreams = &VirtualServerEx{
+		VirtualServerRoutes: []*conf_v1.VirtualServerRoute{
+			{
+				ObjectMeta: meta_v1.ObjectMeta{
+					Name:      "tea-app",
+					Namespace: "tea",
+				},
+				Spec: conf_v1.VirtualServerRouteSpec{
+					Upstreams: []conf_v1.Upstream{
+						{
+							Name:    "tea",
+							Service: "tea-svc",
+						},
+					},
+				},
+			},
+			{
+				ObjectMeta: meta_v1.ObjectMeta{
+					Name:      "coffee-app",
+					Namespace: "coffee",
+				},
+				Spec: conf_v1.VirtualServerRouteSpec{
+					Upstreams: []conf_v1.Upstream{
+						{
+							Name:    "coffee",
+							Service: "coffee-svc",
+						},
+					},
+				},
+			},
+		},
+		VirtualServer: &conf_v1.VirtualServer{
+			ObjectMeta: meta_v1.ObjectMeta{
+				Name:      "cafe-vs",
+				Namespace: "default",
+			},
+			Spec: conf_v1.VirtualServerSpec{
+				Host: "cafe.example.com",
+				Routes: []conf_v1.Route{
+					{
+						Route: "tea/tea",
+					},
+					{
+						Route: "coffee/coffee",
+					},
+				},
+			},
+		},
+	}
 	validTransportServerExWithUpstreams = &TransportServerEx{
 		TransportServer: &conf_v1.TransportServer{
 			ObjectMeta: meta_v1.ObjectMeta{