Skip to content

Commit 3fba4e1

Browse files
committed
feat(ctrl): implement MeshFederation reconciliation
This commit introduces the initial implementation of the MeshFederation controller. The controller is responsible for: - Managing the MeshFederation server lifecycle (openshift-service-mesh#152) - removes a need for a channel to trigger push - pushing directly instead - Configuring MeshFederation resources, including: - IngressGateway - PeerAuthentication - EnvoyFilter (for OpenShift Router) - Routes (for OpenShift Router) - Watching Kubernetes services to: - Push SotW updates to all connected peers (openshift-service-mesh#153) - Update MeshFederation cluster configuration - Support both label selectors and expressions (openshift-service-mesh#52 openshift-service-mesh#143) Basic EnvTest tests are included to verify the setup. Fixes openshift-service-mesh#152 openshift-service-mesh#52 openshift-service-mesh#143 openshift-service-mesh#153
1 parent 8a077b7 commit 3fba4e1

27 files changed

+1946
-46
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ define local_tag
6767
$(TAG)$(shell [ "$(USE_LOCAL_IMAGE)" = "true" ] && echo "-local")
6868
endef
6969

70-
.PHONY: e2e
7170
TEST_SUITES ?= remote_ip remote_dns_name spire
71+
.PHONY: e2e
7272
e2e: kind-clusters ## Runs end-to-end tests against KinD clusters
7373
@local_tag=$(call local_tag); \
7474
$(foreach suite, $(TEST_SUITES), \

Makefile.func.mk

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ define go-mod-version
22
$(shell go mod graph | grep $(1) | head -n 1 | cut -d'@' -f 2)
33
endef
44

5-
# Using controller-gen to fetch external CRDs and put them in defined folder folder
5+
# Using controller-gen to fetch external CRDs and put them in defined folder.
66
# They can be used e.g. in testing using EnvTest where controller under test
77
# requires additional resources to manage.
88
#

api/v1alpha1/meshfederation_types.go

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ type MeshFederationStatus struct {
7474
// Conditions describes the state of the MeshFederation resource.
7575
// +optional
7676
Conditions []metav1.Condition `json:"conditions,omitempty"`
77+
78+
// +optional
79+
ExportedServices []string `json:"exportedServices,omitempty"`
7780
}
7881

7982
type PortConfig struct {

api/v1alpha1/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chart/crds/federation.openshift-service-mesh.io_meshfederations.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ spec:
227227
- type
228228
type: object
229229
type: array
230+
exportedServices:
231+
items:
232+
type: string
233+
type: array
230234
type: object
231235
type: object
232236
served: true

chart/templates/clusterrole.yaml

+5-5
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,23 @@ rules:
1111
verbs: ["get", "list", "create", "update", "patch", "delete"]
1212
- apiGroups: ["security.istio.io"]
1313
resources: ["peerauthentications"]
14-
verbs: ["get", "list", "create", "update", "patch", "delete"]
14+
verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]
1515
{{- if (include "remotes.hasOpenshiftRouterPeer" .) }}
1616
- apiGroups: ["networking.istio.io"]
1717
resources: ["destinationrules"]
18-
verbs: ["get", "list", "create", "update", "patch", "delete"]
18+
verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]
1919
{{- end }}
2020
{{- if eq .Values.federation.meshPeers.local.ingressType "openshift-router" }}
2121
- apiGroups: ["networking.istio.io"]
2222
resources: ["envoyfilters"]
23-
verbs: ["get", "list", "create", "update", "patch", "delete"]
23+
verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]
2424
- apiGroups: ["route.openshift.io"]
2525
resources: ["routes", "routes/custom-host"]
26-
verbs: ["get", "list", "create", "update", "patch", "delete"]
26+
verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]
2727
{{- end }}
2828
- apiGroups: ["federation.openshift-service-mesh.io"]
2929
resources: ["meshfederations", "federatedservices"]
3030
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
3131
- apiGroups: ["federation.openshift-service-mesh.io"]
3232
resources: ["meshfederations/status", "federatedservices/status"]
33-
verbs: ["get"]
33+
verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]

cmd/federation-controller/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func startFDSClient(ctx context.Context, remote config.Remote, meshConfigPushReq
324324
DiscoveryAddr: discoveryAddr,
325325
Authority: remote.ServiceFQDN(),
326326
Handlers: map[string]adsc.ResponseHandler{
327-
xds.ExportedServiceTypeUrl: fds.NewImportedServiceHandler(importedServiceStore, meshConfigPushRequests),
327+
xds.FederatedServiceTypeUrl: fds.NewImportedServiceHandler(importedServiceStore, meshConfigPushRequests),
328328
},
329329
ReconnectDelay: reconnectDelay,
330330
})

docs/arch/diagrams/ctrl-overview.drawio

+706
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright Red Hat, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the License);
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an AS IS BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package meshfederation
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"strings"
21+
"sync"
22+
23+
"google.golang.org/protobuf/proto"
24+
"google.golang.org/protobuf/types/known/anypb"
25+
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/labels"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
protov1alpha1 "github.com/openshift-service-mesh/federation/internal/api/federation/v1alpha1"
30+
"github.com/openshift-service-mesh/federation/internal/pkg/discovery"
31+
)
32+
33+
// TODO(design): should we have one server per MF or single server broadcasting to all -> that would imply recognizing subscribers somehow
34+
// TODO(design): currently we won't be able to run two meshfederations at once due to port conflict
35+
type serverExporter struct {
36+
server *discovery.Server
37+
handler *exportedServicesBroadcaster
38+
}
39+
40+
type serviceExporterRegistry struct {
41+
exporters sync.Map
42+
}
43+
44+
func (r *serviceExporterRegistry) LoadOrStore(name string, serviceExporter *exportedServicesBroadcaster) *discovery.Server {
45+
actual, exists := r.exporters.LoadOrStore(name, serverExporter{
46+
server: discovery.NewServer(serviceExporter),
47+
handler: serviceExporter,
48+
})
49+
50+
exporter := actual.(serverExporter)
51+
if exists {
52+
// update settings
53+
exporter.handler.selector = serviceExporter.selector
54+
}
55+
56+
return exporter.server
57+
}
58+
59+
var _ discovery.RequestHandler = (*exportedServicesBroadcaster)(nil)
60+
61+
type exportedServicesBroadcaster struct {
62+
client client.Client
63+
typeUrl string
64+
selector labels.Selector
65+
}
66+
67+
func (e exportedServicesBroadcaster) GetTypeUrl() string {
68+
return e.typeUrl
69+
}
70+
71+
func (e exportedServicesBroadcaster) GenerateResponse() ([]*anypb.Any, error) {
72+
services := &corev1.ServiceList{}
73+
// TODO: rework ads(s|c) to get ctx?
74+
// We cannot latch into ctx from owning Reconcile call, as it generator can be called from outside reconcile loop
75+
if errSvcList := e.client.List(context.TODO(), services, client.MatchingLabelsSelector{Selector: e.selector}); errSvcList != nil {
76+
return []*anypb.Any{}, errSvcList
77+
}
78+
79+
return convert(services.Items)
80+
}
81+
82+
func convert(services []corev1.Service) ([]*anypb.Any, error) {
83+
var federatedServices []*protov1alpha1.FederatedService
84+
85+
for _, svc := range services {
86+
var ports []*protov1alpha1.ServicePort
87+
for _, port := range svc.Spec.Ports {
88+
servicePort := &protov1alpha1.ServicePort{
89+
Name: port.Name,
90+
Number: uint32(port.Port),
91+
}
92+
if port.TargetPort.IntVal != 0 {
93+
servicePort.TargetPort = uint32(port.TargetPort.IntVal)
94+
}
95+
servicePort.Protocol = detectProtocol(port.Name)
96+
ports = append(ports, servicePort)
97+
}
98+
federatedSvc := &protov1alpha1.FederatedService{
99+
Hostname: fmt.Sprintf("%s.%s.svc.cluster.local", svc.Name, svc.Namespace),
100+
Ports: ports,
101+
Labels: svc.Labels,
102+
}
103+
federatedServices = append(federatedServices, federatedSvc)
104+
}
105+
106+
return serialize(federatedServices)
107+
}
108+
109+
// TODO: check appProtocol and reject UDP
110+
func detectProtocol(portName string) string {
111+
if portName == "https" || strings.HasPrefix(portName, "https-") {
112+
return "HTTPS"
113+
} else if portName == "http" || strings.HasPrefix(portName, "http-") {
114+
return "HTTP"
115+
} else if portName == "http2" || strings.HasPrefix(portName, "http2-") {
116+
return "HTTP2"
117+
} else if portName == "grpc" || strings.HasPrefix(portName, "grpc-") {
118+
return "GRPC"
119+
} else if portName == "tls" || strings.HasPrefix(portName, "tls-") {
120+
return "TLS"
121+
} else if portName == "mongo" || strings.HasPrefix(portName, "mongo-") {
122+
return "MONGO"
123+
}
124+
return "TCP"
125+
}
126+
127+
func serialize(exportedServices []*protov1alpha1.FederatedService) ([]*anypb.Any, error) {
128+
var serializedServices []*anypb.Any
129+
for _, exportedService := range exportedServices {
130+
serializedExportedService := &anypb.Any{}
131+
if err := anypb.MarshalFrom(serializedExportedService, exportedService, proto.MarshalOptions{}); err != nil {
132+
return []*anypb.Any{}, fmt.Errorf("failed to serialize ExportedService %s to protobuf message: %w", exportedService.Hostname, err)
133+
}
134+
serializedServices = append(serializedServices, serializedExportedService)
135+
}
136+
return serializedServices, nil
137+
}

0 commit comments

Comments
 (0)