Skip to content
This repository was archived by the owner on Jul 11, 2023. It is now read-only.

Commit 7953a57

Browse files
authored
Allow all headless services, not just those backed by Statefulsets with subdomains (#5250)
* Allow all headless services, not just those backed by Statefulsets with subdomains Signed-off-by: Keith Mattix II <[email protected]>
1 parent 376a826 commit 7953a57

9 files changed

+390
-20
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ jobs:
241241
matrix:
242242
k8s_version: [""]
243243
focus: [""]
244-
bucket: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
244+
bucket: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
245245
include:
246246
- k8s_version: v1.22.9
247247
focus: "Test traffic flowing from client to server with a Kubernetes Service for the Source: HTTP"

.github/workflows/nightly-fips.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
matrix:
2323
k8s_version: [""]
2424
focus: [""]
25-
bucket: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
25+
bucket: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
2626
include:
2727
- k8s_version: v1.22.9
2828
focus: "Test traffic flowing from client to server with a Kubernetes Service for the Source: HTTP"

pkg/cli/verifier/envoy_config.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
configv1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
2020
"github.com/openservicemesh/osm/pkg/compute/kube"
2121
"github.com/openservicemesh/osm/pkg/envoy"
22+
"github.com/openservicemesh/osm/pkg/k8s"
2223
"github.com/openservicemesh/osm/pkg/trafficpolicy"
2324

2425
"github.com/openservicemesh/osm/pkg/constants"
@@ -479,11 +480,16 @@ func (v *EnvoyConfigVerifier) getDstMeshServicesForK8sSvc(svc corev1.Service) ([
479480
// us to retrieve the TargetPort for the MeshService.
480481
meshSvc.TargetPort = kube.GetTargetPortFromEndpoints(portSpec.Name, *endpoints)
481482

482-
if !kube.IsHeadlessService(svc) {
483+
// Even if the service is headless, add it so it can be targeted
484+
485+
if !k8s.IsHeadlessService(svc) {
483486
meshServices = append(meshServices, meshSvc)
484487
continue
485488
}
486489

490+
// If there's not at least 1 subdomain-ed MeshService added,
491+
// add the entire headless service
492+
var added bool
487493
for _, subset := range endpoints.Subsets {
488494
for _, address := range subset.Addresses {
489495
if address.Hostname == "" {
@@ -498,8 +504,13 @@ func (v *EnvoyConfigVerifier) getDstMeshServicesForK8sSvc(svc corev1.Service) ([
498504
Protocol: meshSvc.Protocol,
499505
}
500506
meshServices = append(meshServices, mSvc)
507+
added = true
501508
}
502509
}
510+
511+
if !added {
512+
meshServices = append(meshServices, meshSvc)
513+
}
503514
}
504515

505516
return meshServices, nil

pkg/compute/kube/client.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -697,11 +697,14 @@ func (c *client) serviceToMeshServices(svc corev1.Service) []service.MeshService
697697
} else {
698698
log.Warn().Msgf("k8s service %s/%s does not have endpoints but is being represented as a MeshService", svc.Namespace, svc.Name)
699699
}
700-
if !IsHeadlessService(svc) || endpoints == nil {
700+
701+
if !k8s.IsHeadlessService(svc) || endpoints == nil {
701702
meshServices = append(meshServices, meshSvc)
702703
continue
703704
}
704-
705+
// If there's not at least 1 subdomain-ed MeshService added,
706+
// add the entire headless service
707+
var added bool
705708
for _, subset := range endpoints.Subsets {
706709
for _, address := range subset.Addresses {
707710
if address.Hostname == "" {
@@ -715,8 +718,13 @@ func (c *client) serviceToMeshServices(svc corev1.Service) []service.MeshService
715718
TargetPort: meshSvc.TargetPort,
716719
Protocol: meshSvc.Protocol,
717720
})
721+
added = true
718722
}
719723
}
724+
725+
if !added {
726+
meshServices = append(meshServices, meshSvc)
727+
}
720728
}
721729
return meshServices
722730
}

pkg/compute/kube/client_test.go

+135-3
Original file line numberDiff line numberDiff line change
@@ -2372,6 +2372,61 @@ func TestServiceToMeshServices(t *testing.T) {
23722372
},
23732373
},
23742374
},
2375+
{
2376+
name: "k8s headless service with single port and endpoint (no hostname), no appProtocol set",
2377+
// Single port on the service maps to a single MeshService.
2378+
// Since no appProtocol is specified, MeshService.Protocol should default
2379+
// to http because Port.Protocol=TCP
2380+
svc: corev1.Service{
2381+
ObjectMeta: metav1.ObjectMeta{
2382+
Namespace: "ns1",
2383+
Name: "s1",
2384+
},
2385+
Spec: corev1.ServiceSpec{
2386+
Ports: []corev1.ServicePort{
2387+
{
2388+
Name: "p1",
2389+
Port: 80,
2390+
Protocol: corev1.ProtocolTCP,
2391+
},
2392+
},
2393+
ClusterIP: corev1.ClusterIPNone,
2394+
},
2395+
},
2396+
svcEndpoints: []runtime.Object{
2397+
&corev1.Endpoints{
2398+
ObjectMeta: metav1.ObjectMeta{
2399+
// Should match svc.Name and svc.Namespace
2400+
Namespace: "ns1",
2401+
Name: "s1",
2402+
},
2403+
Subsets: []corev1.EndpointSubset{
2404+
{
2405+
Addresses: []corev1.EndpointAddress{
2406+
{
2407+
IP: "10.1.0.1",
2408+
},
2409+
},
2410+
Ports: []corev1.EndpointPort{
2411+
{
2412+
// Must match the port of 'svc.Spec.Ports[0]'
2413+
Port: 8080, // TargetPort
2414+
},
2415+
},
2416+
},
2417+
},
2418+
},
2419+
},
2420+
expected: []service.MeshService{
2421+
{
2422+
Namespace: "ns1",
2423+
Name: "s1",
2424+
Port: 80,
2425+
TargetPort: 8080,
2426+
Protocol: "http",
2427+
},
2428+
},
2429+
},
23752430
{
23762431
name: "multiple ports on k8s service with appProtocol specified",
23772432
svc: corev1.Service{
@@ -2514,6 +2569,78 @@ func TestServiceToMeshServices(t *testing.T) {
25142569
},
25152570
},
25162571
},
2572+
{
2573+
name: "multiple ports on k8s headless service with appProtocol specified and no hostname in the endpoints",
2574+
svc: corev1.Service{
2575+
ObjectMeta: metav1.ObjectMeta{
2576+
Namespace: "ns1",
2577+
Name: "s1",
2578+
},
2579+
Spec: corev1.ServiceSpec{
2580+
ClusterIP: corev1.ClusterIPNone,
2581+
Ports: []corev1.ServicePort{
2582+
{
2583+
Name: "p1",
2584+
Port: 80,
2585+
AppProtocol: pointer.StringPtr("http"),
2586+
},
2587+
{
2588+
Name: "p2",
2589+
Port: 90,
2590+
AppProtocol: pointer.StringPtr("tcp"),
2591+
},
2592+
},
2593+
},
2594+
},
2595+
svcEndpoints: []runtime.Object{
2596+
&corev1.Endpoints{
2597+
ObjectMeta: metav1.ObjectMeta{
2598+
// Should match svc.Name and svc.Namespace
2599+
Namespace: "ns1",
2600+
Name: "s1",
2601+
},
2602+
Subsets: []corev1.EndpointSubset{
2603+
{
2604+
Addresses: []corev1.EndpointAddress{
2605+
{
2606+
IP: "10.1.0.1",
2607+
},
2608+
},
2609+
Ports: []corev1.EndpointPort{
2610+
{
2611+
// Must match the port of 'svc.Spec.Ports[0]'
2612+
Name: "p1",
2613+
Port: 8080, // TargetPort
2614+
AppProtocol: pointer.StringPtr("http"),
2615+
},
2616+
{
2617+
// Must match the port of 'svc.Spec.Ports[1]'
2618+
Name: "p2",
2619+
Port: 9090, // TargetPort
2620+
AppProtocol: pointer.StringPtr("tcp"),
2621+
},
2622+
},
2623+
},
2624+
},
2625+
},
2626+
},
2627+
expected: []service.MeshService{
2628+
{
2629+
Namespace: "ns1",
2630+
Name: "s1",
2631+
Port: 80,
2632+
TargetPort: 8080,
2633+
Protocol: "http",
2634+
},
2635+
{
2636+
Namespace: "ns1",
2637+
Name: "s1",
2638+
Port: 90,
2639+
TargetPort: 9090,
2640+
Protocol: "tcp",
2641+
},
2642+
},
2643+
},
25172644
{
25182645
name: "duplicate ports on k8s service with different protocols",
25192646
svc: corev1.Service{
@@ -2682,7 +2809,7 @@ func TestGetMeshService(t *testing.T) {
26822809
expectErr: true,
26832810
},
26842811
{
2685-
name: "TargetPort not found as Endpoint does not exist",
2812+
name: "TargetPort not found as matching Endpoint does not exist",
26862813
svc: &corev1.Service{
26872814
ObjectMeta: metav1.ObjectMeta{
26882815
Name: "s1",
@@ -2693,6 +2820,7 @@ func TestGetMeshService(t *testing.T) {
26932820
Name: "p1",
26942821
Port: 80,
26952822
}},
2823+
ClusterIP: corev1.ClusterIPNone,
26962824
},
26972825
},
26982826
endpoints: &corev1.Endpoints{
@@ -2707,14 +2835,18 @@ func TestGetMeshService(t *testing.T) {
27072835
Name: "invalid", // does not match svc port
27082836
Port: 8080,
27092837
},
2838+
{
2839+
Name: "invalid2",
2840+
Port: 8081, // also does not match svc port; having 2 ports triggers the name filter logic
2841+
},
27102842
},
27112843
},
27122844
},
27132845
},
27142846
namespacedSvc: types.NamespacedName{Namespace: "ns1", Name: "s1"}, // matches svc
27152847
port: 80, // matches svc
2716-
expectedTargetPort: 0, // matches endpoint's 'p1' port
2717-
expectErr: true,
2848+
expectedTargetPort: 0, // does not match either of the endpoint's ports
2849+
expectErr: false, // port matches, so no error should be expected
27182850
},
27192851
}
27202852

pkg/compute/kube/util.go

-6
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,3 @@ func GetTargetPortFromEndpoints(endpointName string, endpoints corev1.Endpoints)
3535
}
3636
return
3737
}
38-
39-
// IsHeadlessService determines whether or not a corev1.Service is a headless service
40-
// TODO(4863): unexport this method, it should not be used outside of this package.
41-
func IsHeadlessService(svc corev1.Service) bool {
42-
return len(svc.Spec.ClusterIP) == 0 || svc.Spec.ClusterIP == corev1.ClusterIPNone
43-
}

tests/e2e/e2e_client_server_connectivity_test.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313

1414
"github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
15+
"github.com/openservicemesh/osm/pkg/tests"
1516

1617
"github.com/openservicemesh/osm/pkg/constants"
1718
. "github.com/openservicemesh/osm/tests/framework"
@@ -26,26 +27,34 @@ var _ = OSMDescribe("Test HTTP traffic from 1 pod client -> 1 pod server",
2627
func() {
2728
Context("Test traffic flowing from client to server with a Kubernetes Service for the Source: HTTP", func() {
2829
withSourceKubernetesService := true
29-
testTraffic(withSourceKubernetesService, PodCommandDefault)
30+
testTraffic(withSourceKubernetesService, PodCommandDefault, false)
3031
})
3132

3233
Context("Test traffic flowing from client to server without a Kubernetes Service for the Source: HTTP", func() {
3334
// Prior iterations of OSM required that a source pod belong to a Kubernetes service
3435
// for the Envoy proxy to be configured for outbound traffic to some remote server.
3536
// This test ensures we test this scenario: client Pod is not associated w/ a service.
3637
withSourceKubernetesService := false
37-
testTraffic(withSourceKubernetesService, PodCommandDefault)
38+
testTraffic(withSourceKubernetesService, PodCommandDefault, false)
3839
})
3940

4041
Context("Test traffic flowing from client to a server with a podIP bind", func() {
4142
// Prior iterations of OSM didn't allow mesh services to bind to the podIP
4243
// This test ensures that that behavior is configurable via MeshConfig
4344
withSourceKubernetesService := true
44-
testTraffic(withSourceKubernetesService, []string{"gunicorn", "-b", "$(POD_IP):80", "httpbin:app", "-k", "gevent"}, WithLocalProxyMode(v1alpha2.LocalProxyModePodIP))
45+
testTraffic(withSourceKubernetesService, []string{"gunicorn", "-b", "$(POD_IP):80", "httpbin:app", "-k", "gevent"}, false, WithLocalProxyMode(v1alpha2.LocalProxyModePodIP))
46+
})
47+
48+
Context("Test traffic flowing from client to a headless service without a Kubernetes Service for the Source: HTTP", func() {
49+
// Prior iterations of OSM required that a source pod belong to a Kubernetes service
50+
// for the Envoy proxy to be configured for outbound traffic to some remote server.
51+
// This test ensures we test this scenario: client Pod is not associated w/ a service.
52+
withSourceKubernetesService := true
53+
testTraffic(withSourceKubernetesService, PodCommandDefault, true)
4554
})
4655
})
4756

48-
func testTraffic(withSourceKubernetesService bool, destPodCommand []string, installOpts ...InstallOsmOpt) {
57+
func testTraffic(withSourceKubernetesService bool, destPodCommand []string, destServiceHeadless bool, installOpts ...InstallOsmOpt) {
4958
const sourceName = "client"
5059
const destName = "server"
5160
var ns = []string{sourceName, destName}
@@ -68,7 +77,7 @@ func testTraffic(withSourceKubernetesService bool, destPodCommand []string, inst
6877
Expect(err).NotTo(HaveOccurred())
6978
_, err = Td.CreatePod(destName, podDef)
7079
Expect(err).NotTo(HaveOccurred())
71-
dstSvc, err := Td.CreateService(destName, svcDef)
80+
dstSvc, err := Td.CreateService(destName, *tests.HeadlessSvc(&svcDef))
7281
Expect(err).NotTo(HaveOccurred())
7382

7483
// Expect it to be up and running in it's receiver namespace

0 commit comments

Comments
 (0)