Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add traffic routing module #543

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions core/route/base/headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package base

type Headers struct {
Request *HeaderOperations
Response *HeaderOperations
}

type HeaderOperations struct {
Set map[string]string
Add map[string]string
Remove []string
}
61 changes: 61 additions & 0 deletions core/route/base/http_match_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package base

import (
"net/url"
"strconv"
)

type HTTPMatchRequest struct {
Name string
Headers map[string]*StringMatch
Uri *StringMatch
Scheme *StringMatch
Authority *StringMatch
Method *StringMatch
Port *int
QueryParams map[string]*StringMatch
}

// TODO
func (h *HTTPMatchRequest) IsMatch(context *TrafficContext) bool {

for key, match := range h.Headers {
if v, ok := context.Headers[key]; ok && !match.IsMatch(v) {
return false
}
}

if h.Uri != nil && !h.Uri.IsMatch(context.Uri) {
return false
}

var parsedURL *url.URL
var err error

if h.Uri != nil || h.Scheme != nil || h.Authority != nil || h.Port != nil {
parsedURL, err = url.Parse(context.Uri)
if err != nil {
return false
}
}
if h.Uri != nil && !h.Uri.IsMatch(parsedURL.Path) {
return false
}
if h.Scheme != nil && !h.Scheme.IsMatch(parsedURL.Scheme) {
return false
}
if h.Authority != nil && !h.Authority.IsMatch(parsedURL.Host) {
return false
}
if h.Port != nil {
p, err := strconv.Atoi(parsedURL.Port())
if err != nil || *h.Port != p {
return false
}
}
if !h.Method.IsMatch(context.MethodName) {
return false
}

return true
}
16 changes: 16 additions & 0 deletions core/route/base/http_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package base

type HTTPRoute struct {
Name string
Match []*HTTPMatchRequest
Route []*HTTPRouteDestination
}

func (h *HTTPRoute) IsMatch(context *TrafficContext) bool {
for _, match := range h.Match {
if match.IsMatch(context) {
return true
}
}
return false
}
20 changes: 20 additions & 0 deletions core/route/base/http_route_destination.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package base

import "fmt"

type HTTPRouteDestination struct {
Weight int
Destination *Destination
Headers Headers // TODO modifies headers
}

func (H HTTPRouteDestination) String() string {
return fmt.Sprintf("{Weight: %v, Destination: %+v}\n", H.Weight, H.Destination)
}

type Destination struct {
Host string
Subset string
Port uint32
Fallback *HTTPRouteDestination
}
9 changes: 9 additions & 0 deletions core/route/base/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package base

type Instance struct {
AppName string
Host string
Port int
Metadata map[string]string
TargetInstance interface{}
}
25 changes: 25 additions & 0 deletions core/route/base/string_match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package base

import "regexp"

type StringMatch struct {
Exact string
Prefix string
Regex string
}

func (s *StringMatch) IsMatch(input string) bool {
if input == "" {
return false
}

if s.Exact != "" {
return input == s.Exact
} else if s.Prefix != "" {
return len(input) >= len(s.Prefix) && input[:len(s.Prefix)] == s.Prefix
} else if s.Regex != "" {
matched, _ := regexp.MatchString(s.Regex, input)
return matched
}
return true
}
14 changes: 14 additions & 0 deletions core/route/base/traffic_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package base

type TrafficContext struct {
Path string
Uri string
ServiceName string
Group string
Version string
MethodName string
ParamTypes []string
Args []interface{}
Headers map[string]string
Baggage map[string]string
}
5 changes: 5 additions & 0 deletions core/route/base/traffic_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package base

type TrafficPolicy struct {
LoadBalancer string
}
11 changes: 11 additions & 0 deletions core/route/base/traffic_router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package base

type TrafficRouter struct {
Host []string
Http []*HTTPRoute
}

type Fallback struct {
Host string
Subset string
}
12 changes: 12 additions & 0 deletions core/route/base/virtual_workload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package base

type VirtualWorkload struct {
Host string
trafficPolicy *TrafficPolicy
Subsets []*Subset
}

type Subset struct {
Name string
Labels map[string]string
}
51 changes: 51 additions & 0 deletions core/route/cluster_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package route

import (
"github.com/alibaba/sentinel-golang/core/route/base"
"github.com/pkg/errors"
)

type ClusterManager struct {
InstanceManager InstanceManager
RouterFilterList []RouterFilter
LoadBalancer LoadBalancer
}

func NewClusterManager(instanceManager InstanceManager, routerFilters []RouterFilter, loadBalancer LoadBalancer) *ClusterManager {
return &ClusterManager{
InstanceManager: instanceManager,
RouterFilterList: routerFilters,
LoadBalancer: loadBalancer,
}
}

func (m *ClusterManager) Route(context *base.TrafficContext) ([]*base.Instance, error) {
instances := m.InstanceManager.GetInstances()

var err error
for _, routerFilter := range m.RouterFilterList {
instances, err = routerFilter.Filter(instances, context)
if err != nil {
return nil, err
}
}
if len(instances) == 0 {
return nil, errors.New("no matching instances")
}
return instances, nil
}

func (m *ClusterManager) GetOne(context *base.TrafficContext) (*base.Instance, error) {
instances, err := m.Route(context)
if err != nil {
return nil, err
}
if m.LoadBalancer == nil {
return instances[0], nil
}
instance, err := m.LoadBalancer.Select(instances, context)
if err != nil {
return nil, err
}
return instance, nil
}
146 changes: 146 additions & 0 deletions core/route/datasource/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package datasource

import (
"github.com/alibaba/sentinel-golang/core/route/base"
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
"sort"
"strings"
)

// resolveRouting parses envoy RouteConfiguration to TrafficRouters and VirtualWorkloads
func resolveRouting(configurations []routev3.RouteConfiguration) ([]*base.TrafficRouter, []*base.VirtualWorkload) {
var routerList []*base.TrafficRouter
virtualWorkloadMap := map[string]*base.VirtualWorkload{}
subsetMap := map[string]map[string]*base.Subset{}
for i := range configurations {
conf := &configurations[i]
virtualHosts := conf.GetVirtualHosts()
for _, virtualHost := range virtualHosts {
appName := strings.Split(virtualHost.GetName(), ".")[0]
router := &base.TrafficRouter{}
router.Host = append(router.Host, appName)

for _, route := range virtualHost.GetRoutes() {
dest, subsets := convertRouteAction(route.GetRoute(), appName)
httpRoute := &base.HTTPRoute{
Name: route.GetName(),
Match: []*base.HTTPMatchRequest{convertRouteMatch(route.GetMatch())},
Route: dest,
}
router.Http = append(router.Http, httpRoute)

// add VirtualWorkload
if _, ok := virtualWorkloadMap[appName]; !ok {
virtualWorkloadMap[appName] = &base.VirtualWorkload{
Host: appName,
}
}
for subset := range subsets {
if _, ok := subsetMap[appName]; !ok {
subsetMap[appName] = map[string]*base.Subset{}
}
if _, ok := subsetMap[appName][subset]; !ok {
subsetMap[appName][subset] = &base.Subset{
Name: subset,
}
}
}
}
routerList = append(routerList, router)
}
}

// build VirtualWorkload list
var vwList []*base.VirtualWorkload
for vw, m := range subsetMap {
virtualWorkloadMap[vw].Subsets = make([]*base.Subset, 0, len(m))
for _, s := range m {
virtualWorkloadMap[vw].Subsets = append(virtualWorkloadMap[vw].Subsets, s)
}
}
for _, vw := range virtualWorkloadMap {
vwList = append(vwList, vw)
}
sort.Slice(vwList, func(i, j int) bool {
return vwList[i].Host < vwList[j].Host
})
for _, vw := range vwList {
sort.Slice(vw.Subsets, func(i, j int) bool {
return vw.Subsets[i].Name < vw.Subsets[j].Name
})
}
return routerList, vwList
}

func convertRouteMatch(match *routev3.RouteMatch) *base.HTTPMatchRequest {
mr := &base.HTTPMatchRequest{
Headers: make(map[string]*base.StringMatch),
}
for _, m := range match.Headers {
mr.Headers[m.GetName()] = convertHeaderMatcher(m)
}
return mr
}

func convertHeaderMatcher(matcher *routev3.HeaderMatcher) *base.StringMatch {
// supports PresentMatch, ExactMatch, PrefixMatch, RegexMatch for now
if matcher.GetPresentMatch() {
return &base.StringMatch{Regex: ".*"}
}
sm := matcher.GetStringMatch()
if sm == nil {
return nil
}
if sm.GetExact() != "" {
return &base.StringMatch{Exact: sm.GetExact()}
}
if sm.GetPrefix() != "" {
return &base.StringMatch{Prefix: sm.GetPrefix()}
}
if sm.GetSafeRegex() != nil && sm.GetSafeRegex().Regex != "" {
return &base.StringMatch{Regex: sm.GetSafeRegex().Regex}
}
return nil
}

func convertRouteAction(dest *routev3.RouteAction, host string) ([]*base.HTTPRouteDestination, map[string]bool) {
// supports Cluster, WeightedClusters for now
if dest.GetCluster() != "" {
subset := getSubset(dest.GetCluster())
return []*base.HTTPRouteDestination{
{
Weight: 1,
Destination: &base.Destination{
Host: host,
Subset: subset,
},
},
}, map[string]bool{subset: true}
}
if dest.GetWeightedClusters() != nil {
var destList []*base.HTTPRouteDestination
subsets := make(map[string]bool)
for _, cluster := range dest.GetWeightedClusters().Clusters {
subset := getSubset(cluster.GetName())
subsets[subset] = true
destList = append(destList, &base.HTTPRouteDestination{
Weight: int(cluster.GetWeight().GetValue()),
Destination: &base.Destination{
Host: host,
Subset: subset,
},
})
}
return destList, subsets
}
return nil, nil
}

func getSubset(cluster string) string {
version := ""
info := strings.Split(cluster, "|")
if len(info) >= 3 {
version = info[2]
}
return version
}
Loading