mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-19 03:21:11 +02:00
Use github.com/siderolabs/gen Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
142 lines
4.5 KiB
Go
142 lines
4.5 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
// Package director provides proxy call routing facility
|
|
package director
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/siderolabs/gen/slices"
|
|
"github.com/talos-systems/grpc-proxy/proxy"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/metadata"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// Router wraps grpc-proxy StreamDirector.
|
|
type Router struct {
|
|
localBackend proxy.Backend
|
|
remoteBackendFactory RemoteBackendFactory
|
|
localAddressProvider LocalAddressProvider
|
|
streamedMatchers []*regexp.Regexp
|
|
}
|
|
|
|
// RemoteBackendFactory provides backend generation by address (target).
|
|
type RemoteBackendFactory func(target string) (proxy.Backend, error)
|
|
|
|
// NewRouter builds new Router.
|
|
func NewRouter(backendFactory RemoteBackendFactory, localBackend proxy.Backend, localAddressProvider LocalAddressProvider) *Router {
|
|
return &Router{
|
|
localBackend: localBackend,
|
|
remoteBackendFactory: backendFactory,
|
|
localAddressProvider: localAddressProvider,
|
|
}
|
|
}
|
|
|
|
// Register is no-op to implement factory.Registrator interface.
|
|
//
|
|
// Actual proxy handler is installed via grpc.UnknownServiceHandler option.
|
|
func (r *Router) Register(srv *grpc.Server) {
|
|
}
|
|
|
|
// Director implements proxy.StreamDirector function.
|
|
//
|
|
//nolint:gocyclo
|
|
func (r *Router) Director(ctx context.Context, fullMethodName string) (proxy.Mode, []proxy.Backend, error) {
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
|
if !ok {
|
|
return proxy.One2One, []proxy.Backend{r.localBackend}, nil
|
|
}
|
|
|
|
if _, exists := md["proxyfrom"]; exists {
|
|
return proxy.One2One, []proxy.Backend{r.localBackend}, nil
|
|
}
|
|
|
|
nodes, okNodes := md["nodes"]
|
|
node, okNode := md["node"]
|
|
|
|
if okNode && len(node) != 1 {
|
|
return proxy.One2One, nil, status.Error(codes.InvalidArgument, "node metadata must be single-valued")
|
|
}
|
|
|
|
// special handling for cases when a single node is requested, but forwarding is disabled
|
|
//
|
|
// if there's a single destination, and that destination is local node, skip forwarding and send a request to the same node
|
|
if r.remoteBackendFactory == nil {
|
|
if okNode && r.localAddressProvider.IsLocalTarget(node[0]) {
|
|
okNode = false
|
|
}
|
|
|
|
if okNodes && len(nodes) == 1 && r.localAddressProvider.IsLocalTarget(nodes[0]) {
|
|
okNodes = false
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case okNodes:
|
|
// COSI methods do not support one-2-many proxying.
|
|
if strings.HasPrefix(fullMethodName, "/cosi.") {
|
|
return proxy.One2One, nil, status.Error(codes.InvalidArgument, "one-2-many proxying is not supported for COSI methods")
|
|
}
|
|
|
|
return r.aggregateDirector(nodes)
|
|
case okNode:
|
|
return r.singleDirector(node[0])
|
|
default:
|
|
// send directly to local node, skips another layer of proxying
|
|
return proxy.One2One, []proxy.Backend{r.localBackend}, nil
|
|
}
|
|
}
|
|
|
|
// singleDirector sends request to a single instance in one-2-one mode.
|
|
func (r *Router) singleDirector(target string) (proxy.Mode, []proxy.Backend, error) {
|
|
if r.remoteBackendFactory == nil {
|
|
return proxy.One2One, nil, status.Error(codes.PermissionDenied, "no request forwarding")
|
|
}
|
|
|
|
backend, err := r.remoteBackendFactory(target)
|
|
if err != nil {
|
|
return proxy.One2One, nil, status.Error(codes.Internal, err.Error())
|
|
}
|
|
|
|
return proxy.One2One, []proxy.Backend{backend}, nil
|
|
}
|
|
|
|
// aggregateDirector sends request across set of remote instances and aggregates results.
|
|
func (r *Router) aggregateDirector(targets []string) (proxy.Mode, []proxy.Backend, error) {
|
|
if r.remoteBackendFactory == nil {
|
|
return proxy.One2One, nil, status.Error(codes.PermissionDenied, "no request forwarding")
|
|
}
|
|
|
|
var err error
|
|
|
|
backends := make([]proxy.Backend, len(targets))
|
|
|
|
for i, target := range targets {
|
|
backends[i], err = r.remoteBackendFactory(target)
|
|
if err != nil {
|
|
return proxy.One2Many, nil, status.Error(codes.Internal, err.Error())
|
|
}
|
|
}
|
|
|
|
return proxy.One2Many, backends, nil
|
|
}
|
|
|
|
// StreamedDetector implements proxy.StreamedDetector.
|
|
func (r *Router) StreamedDetector(fullMethodName string) bool {
|
|
return slices.Contains(r.streamedMatchers, func(regex *regexp.Regexp) bool { return regex.MatchString(fullMethodName) })
|
|
}
|
|
|
|
// RegisterStreamedRegex register regex for streamed method.
|
|
//
|
|
// This could be exact literal match: /^\/serviceName\/methodName$/ or any
|
|
// suffix/prefix match.
|
|
func (r *Router) RegisterStreamedRegex(regex string) {
|
|
r.streamedMatchers = append(r.streamedMatchers, regexp.MustCompile(regex))
|
|
}
|