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

Commit 0af42df

Browse files
authored
(feat/statefulsets): MeshService API changes for Headless Services (#4704)
* Introduce service.ProviderMapper to MeshService As a stepping stone for statefulset support (#3477), introduce a new interface describing the ability to map an entity back to a provider service (e.g. a Kubernetes service). This decouples the MeshService name from being a foreign key between the provider's collection of services and the MeshCatalog's set of services Signed-off-by: Keith Mattix II <[email protected]> * Add subdomain field to Meshservice Signed-off-by: Keith Mattix II <[email protected]> * Write tests for headless service functionality Signed-off-by: Keith Mattix II <[email protected]> * Address PR comments Signed-off-by: Keith Mattix II <[email protected]> * Filter MeshServices from headless service based on subdomain When retrieving MeshServices in order to create local clusters for a pod, exclude MeshServices whose subdomains don't match the pod's name Signed-off-by: Keith Mattix II <[email protected]> * Implement MeshService creation functions Tracking the unexported MeshService fields is difficult, and there are several hidden bugs that can occur depending on what fields are accessed. So, we create the NewMeshService and NewPartialMeshService functions to aid in correct usage Signed-off-by: Keith Mattix II <[email protected]> * Fix arg order Signed-off-by: Keith Mattix II <[email protected]> * Remove unnecessary function & newline Signed-off-by: Keith Mattix II <[email protected]> * Re-duplicate svc to meshsvc code and de-memoize MeshService Signed-off-by: Keith Mattix II <[email protected]> * Remove unneccesary Equals function Signed-off-by: Keith Mattix II <[email protected]> * Remove unneeded newline Signed-off-by: Keith Mattix II <[email protected]> * Add mock expectation Signed-off-by: Keith Mattix II <[email protected]> * Add kubecontroller to splitHostname functions Signed-off-by: Keith Mattix II <[email protected]> * Add mock test expectation Signed-off-by: Keith Mattix II <[email protected]> * Fix tests Signed-off-by: Keith Mattix II <[email protected]> * Fix tests again Signed-off-by: Keith Mattix II <[email protected]> * Address PR comments Signed-off-by: Keith Mattix II <[email protected]> * Remove unneded function Signed-off-by: Keith Mattix II <[email protected]> * Comment exported function Signed-off-by: Keith Mattix II <[email protected]> * Additional PR fixes Signed-off-by: Keith Mattix II <[email protected]> * Simplify tests Signed-off-by: Keith Mattix II <[email protected]>
1 parent 3395da5 commit 0af42df

26 files changed

+954
-340
lines changed

pkg/catalog/inbound_traffic_policies.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,20 @@ func (mc *MeshCatalog) getUpstreamServicesIncludeApex(upstreamServices []service
244244
}
245245

246246
for _, split := range mc.meshSpec.ListTrafficSplits(smi.WithTrafficSplitBackendService(svc)) {
247+
svcName := k8s.GetServiceFromHostname(mc.kubeController, split.Spec.Service)
248+
subdomain := k8s.GetSubdomainFromHostname(mc.kubeController, split.Spec.Service)
247249
apexMeshService := service.MeshService{
248250
Namespace: svc.Namespace,
249-
Name: k8s.GetServiceFromHostname(split.Spec.Service),
251+
Name: svcName,
250252
Port: svc.Port,
251253
TargetPort: svc.TargetPort,
252254
Protocol: svc.Protocol,
253255
}
256+
257+
if subdomain != "" {
258+
apexMeshService.Name = fmt.Sprintf("%s.%s", subdomain, svcName)
259+
}
260+
254261
if newlyAdded := svcSet.Add(apexMeshService); newlyAdded {
255262
allServices = append(allServices, apexMeshService)
256263
}

pkg/catalog/inbound_traffic_policies_test.go

+102
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func TestGetInboundMeshTrafficPolicy(t *testing.T) {
3535
permissiveMode bool
3636
trafficTargets []*access.TrafficTarget
3737
httpRouteGroups []*spec.HTTPRouteGroup
38+
tcpRoutes []*spec.TCPRoute
3839
trafficSplits []*split.TrafficSplit
3940
prepare func(mockMeshSpec *smi.MockMeshSpec, trafficSplits []*split.TrafficSplit)
4041
expectedInboundMeshPolicy *trafficpolicy.InboundMeshTrafficPolicy
@@ -213,6 +214,104 @@ func TestGetInboundMeshTrafficPolicy(t *testing.T) {
213214
},
214215
},
215216
},
217+
{
218+
name: "multiple services, statefulset, SMI mode, 1 TrafficTarget, 1 TCPRoute, 0 TrafficSplit",
219+
upstreamIdentity: upstreamSvcAccount.ToServiceIdentity(),
220+
upstreamServices: []service.MeshService{
221+
{
222+
Name: "mysql-0.mysql",
223+
Namespace: "ns1",
224+
Port: 3306,
225+
TargetPort: 3306,
226+
Protocol: "tcp",
227+
},
228+
{
229+
Name: "s2",
230+
Namespace: "ns1",
231+
Port: 90,
232+
TargetPort: 9090,
233+
Protocol: "http",
234+
},
235+
},
236+
permissiveMode: false,
237+
trafficTargets: []*access.TrafficTarget{
238+
{
239+
TypeMeta: metav1.TypeMeta{
240+
APIVersion: "access.smi-spec.io/v1alpha3",
241+
Kind: "TrafficTarget",
242+
},
243+
ObjectMeta: metav1.ObjectMeta{
244+
Name: "t1",
245+
Namespace: "ns1",
246+
},
247+
Spec: access.TrafficTargetSpec{
248+
Destination: access.IdentityBindingSubject{
249+
Kind: "ServiceAccount",
250+
Name: "sa1",
251+
Namespace: "ns1",
252+
},
253+
Sources: []access.IdentityBindingSubject{{
254+
Kind: "ServiceAccount",
255+
Name: "sa2",
256+
Namespace: "ns2",
257+
}},
258+
Rules: []access.TrafficTargetRule{{
259+
Kind: "TCPRoute",
260+
Name: "rule-1",
261+
}},
262+
},
263+
},
264+
},
265+
tcpRoutes: []*spec.TCPRoute{
266+
{
267+
TypeMeta: metav1.TypeMeta{
268+
APIVersion: "specs.smi-spec.io/v1alpha4",
269+
Kind: "TCPRoute",
270+
},
271+
ObjectMeta: metav1.ObjectMeta{
272+
Namespace: "ns1",
273+
Name: "rule-1",
274+
},
275+
Spec: spec.TCPRouteSpec{
276+
Matches: spec.TCPMatch{
277+
Ports: []int{3306},
278+
},
279+
},
280+
},
281+
},
282+
trafficSplits: nil,
283+
prepare: func(mockMeshSpec *smi.MockMeshSpec, trafficSplits []*split.TrafficSplit) {
284+
mockMeshSpec.EXPECT().ListTrafficSplits(gomock.Any()).Return(trafficSplits).AnyTimes()
285+
},
286+
expectedInboundMeshPolicy: &trafficpolicy.InboundMeshTrafficPolicy{
287+
TrafficMatches: []*trafficpolicy.TrafficMatch{
288+
{
289+
Name: "inbound_ns1/mysql-0.mysql_3306_tcp",
290+
DestinationPort: 3306,
291+
DestinationProtocol: "tcp",
292+
},
293+
{
294+
Name: "inbound_ns1/s2_9090_http",
295+
DestinationPort: 9090,
296+
DestinationProtocol: "http",
297+
},
298+
},
299+
ClustersConfigs: []*trafficpolicy.MeshClusterConfig{
300+
{
301+
Name: "ns1/mysql-0.mysql|3306|local",
302+
Service: service.MeshService{Namespace: "ns1", Name: "mysql-0.mysql", Port: 3306, TargetPort: 3306, Protocol: "tcp"},
303+
Address: "127.0.0.1",
304+
Port: 3306,
305+
},
306+
{
307+
Name: "ns1/s2|9090|local",
308+
Service: service.MeshService{Namespace: "ns1", Name: "s2", Port: 90, TargetPort: 9090, Protocol: "http"},
309+
Address: "127.0.0.1",
310+
Port: 9090,
311+
},
312+
},
313+
},
314+
},
216315
{
217316
name: "multiple services, SMI mode, 1 TrafficTarget, multiple HTTPRouteGroup, 0 TrafficSplit",
218317
upstreamIdentity: upstreamSvcAccount.ToServiceIdentity(),
@@ -1614,6 +1713,9 @@ func TestGetInboundMeshTrafficPolicy(t *testing.T) {
16141713
for expectedKey, expectedVal := range tc.expectedInboundMeshPolicy.HTTPRouteConfigsPerPort {
16151714
assert.ElementsMatch(expectedVal, actual.HTTPRouteConfigsPerPort[expectedKey])
16161715
}
1716+
if len(tc.expectedInboundMeshPolicy.TrafficMatches) != 0 {
1717+
assert.ElementsMatch(tc.expectedInboundMeshPolicy.TrafficMatches, actual.TrafficMatches)
1718+
}
16171719
})
16181720
}
16191721
}

pkg/catalog/ingress.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ func (mc *MeshCatalog) GetIngressTrafficPolicy(svc service.MeshService) (*traffi
6363
for _, source := range ingressBackendPolicy.Spec.Sources {
6464
switch source.Kind {
6565
case policyV1alpha1.KindService:
66-
sourceMeshSvc := service.MeshService{Name: source.Name, Namespace: source.Namespace}
66+
sourceMeshSvc := service.MeshService{
67+
Name: source.Name,
68+
Namespace: source.Namespace,
69+
}
6770
endpoints := mc.listEndpointsForService(sourceMeshSvc)
6871
if len(endpoints) == 0 {
6972
ingressBackendWithStatus.Status = policyV1alpha1.IngressBackendStatus{

pkg/catalog/outbound_traffic_policies.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (mc *MeshCatalog) listAllowedUpstreamServicesIncludeApex(downstreamIdentity
218218
}
219219
for _, backend := range split.Spec.Backends {
220220
if backend.Service == upstreamSvc.Name {
221-
rootServiceName := k8s.GetServiceFromHostname(split.Spec.Service)
221+
rootServiceName := k8s.GetServiceFromHostname(mc.kubeController, split.Spec.Service)
222222
rootMeshService := service.MeshService{
223223
Namespace: split.Namespace,
224224
Name: rootServiceName,

pkg/catalog/outbound_traffic_policies_test.go

+34-25
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func TestGetOutboundMeshTrafficPolicy(t *testing.T) {
141141
Namespace: "ns3",
142142
},
143143
Spec: split.TrafficSplitSpec{
144-
Service: "s3.ns3.cluster.local",
144+
Service: "s3.ns3.svc.cluster.local",
145145
Backends: []split.TrafficSplitBackend{
146146
{
147147
Service: "s3-v1",
@@ -767,29 +767,13 @@ func TestGetDestinationServicesFromTrafficTarget(t *testing.T) {
767767
}
768768

769769
func TestListAllowedUpstreamServicesIncludeApex(t *testing.T) {
770-
mockCtrl := gomock.NewController(t)
771-
defer mockCtrl.Finish()
772-
773-
mockMeshSpec := smi.NewMockMeshSpec(mockCtrl)
774-
mockConfigurator := configurator.NewMockConfigurator(mockCtrl)
775-
mockController := k8s.NewMockController(mockCtrl)
776-
mockServiceProvider := service.NewMockProvider(mockCtrl)
777-
mockConfigurator.EXPECT().GetFeatureFlags().Return(configv1alpha2.FeatureFlags{EnableMulticlusterMode: true}).AnyTimes()
778-
mockConfigurator.EXPECT().GetOSMNamespace().Return("osm-system").AnyTimes()
779-
780-
mc := MeshCatalog{
781-
meshSpec: mockMeshSpec,
782-
kubeController: mockController,
783-
configurator: mockConfigurator,
784-
serviceProviders: []service.Provider{mockServiceProvider},
785-
}
786-
787770
testCases := []struct {
788-
name string
789-
id identity.ServiceIdentity
790-
services []*corev1.Service
791-
trafficSplits []*split.TrafficSplit
792-
expected []service.MeshService
771+
name string
772+
id identity.ServiceIdentity
773+
services []*corev1.Service
774+
trafficSplits []*split.TrafficSplit
775+
expected []service.MeshService
776+
foundNamespace bool
793777
}{
794778
{
795779
name: "no allowed outbound services",
@@ -857,6 +841,7 @@ func TestListAllowedUpstreamServicesIncludeApex(t *testing.T) {
857841
},
858842
},
859843
},
844+
foundNamespace: true,
860845
expected: []service.MeshService{
861846
{
862847
Name: "split-svc",
@@ -882,8 +867,9 @@ func TestListAllowedUpstreamServicesIncludeApex(t *testing.T) {
882867
},
883868
},
884869
{
885-
name: "TrafficSplit apex service should not have duplicate when it does not have endpoints",
886-
id: "my-src-ns.my-src-name",
870+
name: "TrafficSplit apex service should not have duplicate when it does not have endpoints",
871+
id: "my-src-ns.my-src-name",
872+
foundNamespace: true,
887873
services: []*corev1.Service{
888874
{
889875
ObjectMeta: metav1.ObjectMeta{
@@ -981,6 +967,22 @@ func TestListAllowedUpstreamServicesIncludeApex(t *testing.T) {
981967

982968
for _, tc := range testCases {
983969
t.Run(tc.name, func(t *testing.T) {
970+
mockCtrl := gomock.NewController(t)
971+
defer mockCtrl.Finish()
972+
973+
mockMeshSpec := smi.NewMockMeshSpec(mockCtrl)
974+
mockConfigurator := configurator.NewMockConfigurator(mockCtrl)
975+
mockController := k8s.NewMockController(mockCtrl)
976+
mockServiceProvider := service.NewMockProvider(mockCtrl)
977+
mockConfigurator.EXPECT().GetFeatureFlags().Return(configv1alpha2.FeatureFlags{EnableMulticlusterMode: true}).AnyTimes()
978+
mockConfigurator.EXPECT().GetOSMNamespace().Return("osm-system").AnyTimes()
979+
980+
mc := MeshCatalog{
981+
meshSpec: mockMeshSpec,
982+
kubeController: mockController,
983+
configurator: mockConfigurator,
984+
serviceProviders: []service.Provider{mockServiceProvider},
985+
}
984986
var meshServices []service.MeshService
985987

986988
for _, k8sSvc := range tc.services {
@@ -993,6 +995,13 @@ func TestListAllowedUpstreamServicesIncludeApex(t *testing.T) {
993995
mockMeshSpec.EXPECT().ListTrafficSplits().Return(tc.trafficSplits).Times(1)
994996
}
995997

998+
var ns *corev1.Namespace
999+
if tc.foundNamespace {
1000+
ns = &corev1.Namespace{}
1001+
}
1002+
1003+
mockController.EXPECT().GetNamespace(gomock.Any()).Return(ns).AnyTimes()
1004+
9961005
tassert.ElementsMatch(t, tc.expected, mc.listAllowedUpstreamServicesIncludeApex(tc.id))
9971006
})
9981007
}

pkg/catalog/retry.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ func (mc *MeshCatalog) getRetryPolicy(downstreamIdentity identity.ServiceIdentit
2828
log.Error().Msgf("Retry policy destinations must be a service: %s is a %s", dest, dest.Kind)
2929
continue
3030
}
31-
32-
if upstreamSvc.Name == dest.Name && upstreamSvc.Namespace == dest.Namespace {
31+
destMeshSvc := service.MeshService{Name: dest.Name, Namespace: dest.Namespace}
32+
// we want all statefulset replicas to have the same retry policy regardless of how they're accessed
33+
// for the default use-case, this is equivalent to a name + namespace equality check
34+
if upstreamSvc.SiblingTo(destMeshSvc) {
3335
// Will return retry policy that applies to the specific upstream service
3436
return &retryCRD.Spec.RetryPolicy
3537
}

0 commit comments

Comments
 (0)