Rainbond/node/core/envoy/v1/resource.go

1125 lines
34 KiB
Go

// RAINBOND, Application Management Platform
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package v1
import (
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/duration"
)
const (
// DefaultAccessLog is the name of the log channel (stdout in docker environment)
DefaultAccessLog = "/dev/stdout"
// DefaultLbType defines the default load balancer policy
DefaultLbType = LbTypeRoundRobin
// LDSName is the name of listener-discovery-service (LDS) cluster
LDSName = "lds"
// RDSName is the name of route-discovery-service (RDS) cluster
RDSName = "rds"
// SDSName is the name of service-discovery-service (SDS) cluster
SDSName = "sds"
// CDSName is the name of cluster-discovery-service (CDS) cluster
CDSName = "cds"
// RDSAll is the special name for HTTP PROXY route
RDSAll = "http_proxy"
// VirtualListenerName is the name for traffic capture listener
VirtualListenerName = "virtual"
// ClusterTypeStrictDNS name for clusters of type 'strict_dns'
ClusterTypeStrictDNS = "strict_dns"
// ClusterTypeStatic name for clusters of type 'static'
ClusterTypeStatic = "static"
// ClusterTypeOriginalDST name for clusters of type 'original_dst'
ClusterTypeOriginalDST = "original_dst"
// ClusterTypeSDS name for clusters of type 'sds'
ClusterTypeSDS = "sds"
// LbTypeRoundRobin is the name for round-robin LB
LbTypeRoundRobin = "round_robin"
// LbTypeLeastRequest is the name for least request LB
LbTypeLeastRequest = "least_request"
// LbTypeRingHash is the name for ring hash LB
LbTypeRingHash = "ring_hash"
// LbTypeRandom is the name for random LB
LbTypeRandom = "random"
// LbTypeOriginalDST is the name for LB of original_dst
LbTypeOriginalDST = "original_dst_lb"
// ClusterFeatureHTTP2 is the feature to use HTTP/2 for a cluster
ClusterFeatureHTTP2 = "http2"
// HTTPConnectionManager is the name of HTTP filter.
HTTPConnectionManager = "http_connection_manager"
// TCPProxyFilter is the name of the TCP Proxy network filter.
TCPProxyFilter = "tcp_proxy"
// CORSFilter is the name of the CORS network filter
CORSFilter = "cors"
// MongoProxyFilter is the name of the Mongo Proxy network filter.
MongoProxyFilter = "mongo_proxy"
// RedisProxyFilter is the name of the Redis Proxy network filter.
RedisProxyFilter = "redis_proxy"
// RedisDefaultOpTimeout is the op timeout used for Redis Proxy filter
// Currently it is set to 30s (conversion happens in the filter)
// TODO - Allow this to be configured.
RedisDefaultOpTimeout = 30 * time.Second
// WildcardAddress binds to all IP addresses
WildcardAddress = "0.0.0.0"
// LocalhostAddress for local binding
LocalhostAddress = "127.0.0.1"
// EgressTraceOperation denotes the name of trace operation for Envoy
EgressTraceOperation = "egress"
// IngressTraceOperation denotes the name of trace operation for Envoy
IngressTraceOperation = "ingress"
// ZipkinTraceDriverType denotes the Zipkin HTTP trace driver
ZipkinTraceDriverType = "zipkin"
// ZipkinCollectorCluster denotes the cluster where zipkin server is running
ZipkinCollectorCluster = "zipkin"
// ZipkinCollectorEndpoint denotes the REST endpoint where Envoy posts Zipkin spans
ZipkinCollectorEndpoint = "/api/v1/spans"
// MaxClusterNameLength is the maximum cluster name length
MaxClusterNameLength = 189 // TODO: use MeshConfig.StatNameLength instead
// Headers with special meaning in Envoy
// HeaderMethod is the method header.
HeaderMethod = ":method"
// HeaderAuthority is the authority header.
HeaderAuthority = ":authority"
// HeaderScheme is the scheme header.
HeaderScheme = ":scheme"
// MixerFilter name and its attributes
MixerFilter = "mixer"
router = "router"
auto = "auto"
decoder = "decoder"
read = "read"
both = "both"
)
var (
// ValidateClusters is an environment variable that can be set to false to disable
// cluster validation in RDS, in case problems are discovered.
ValidateClusters = true
)
// ListenersALPNProtocols denotes the the list of ALPN protocols that the listener
// should expose
var ListenersALPNProtocols = []string{"h2", "http/1.1"}
// convertDuration converts to golang duration and logs errors
func convertDuration(d *duration.Duration) time.Duration {
if d == nil {
return 0
}
dur, err := ptypes.Duration(d)
if err != nil {
logrus.Warnf("error converting duration %#v, using 0: %v", d, err)
}
return dur
}
func protoDurationToMS(dur *duration.Duration) int64 {
return int64(convertDuration(dur) / time.Millisecond)
}
// Config defines the schema for Envoy JSON configuration format
type Config struct {
RootRuntime *RootRuntime `json:"runtime,omitempty"`
Listeners Listeners `json:"listeners"`
LDS *LDSCluster `json:"lds,omitempty"`
Admin Admin `json:"admin"`
ClusterManager ClusterManager `json:"cluster_manager"`
StatsdUDPIPAddress string `json:"statsd_udp_ip_address,omitempty"`
Tracing *Tracing `json:"tracing,omitempty"`
// Special value used to hash all referenced values (e.g. TLS secrets)
Hash []byte `json:"-"`
}
// Tracing definition
type Tracing struct {
HTTPTracer HTTPTracer `json:"http"`
}
// HTTPTracer definition
type HTTPTracer struct {
HTTPTraceDriver HTTPTraceDriver `json:"driver"`
}
// HTTPTraceDriver definition
type HTTPTraceDriver struct {
HTTPTraceDriverType string `json:"type"`
HTTPTraceDriverConfig HTTPTraceDriverConfig `json:"config"`
}
// HTTPTraceDriverConfig definition
type HTTPTraceDriverConfig struct {
CollectorCluster string `json:"collector_cluster"`
CollectorEndpoint string `json:"collector_endpoint"`
}
// RootRuntime definition.
// See https://envoyproxy.github.io/envoy/configuration/overview/overview.html
type RootRuntime struct {
SymlinkRoot string `json:"symlink_root"`
Subdirectory string `json:"subdirectory"`
OverrideSubdirectory string `json:"override_subdirectory,omitempty"`
}
// AbortFilter definition
type AbortFilter struct {
Percent int `json:"abort_percent,omitempty"`
HTTPStatus int `json:"http_status,omitempty"`
}
// DelayFilter definition
type DelayFilter struct {
Type string `json:"type,omitempty"`
Percent int `json:"fixed_delay_percent,omitempty"`
Duration int64 `json:"fixed_duration_ms,omitempty"`
}
// AppendedHeader definition
type AppendedHeader struct {
Key string `json:"key"`
Value string `json:"value"`
}
// Header definition
type Header struct {
Name string `json:"name"`
Value string `json:"value"`
Regex bool `json:"regex,omitempty"`
}
// FilterFaultConfig definition
type FilterFaultConfig struct {
Abort *AbortFilter `json:"abort,omitempty"`
Delay *DelayFilter `json:"delay,omitempty"`
Headers Headers `json:"headers,omitempty"`
UpstreamCluster string `json:"upstream_cluster,omitempty"`
}
// FilterRouterConfig definition
type FilterRouterConfig struct {
// DynamicStats defaults to true
DynamicStats bool `json:"dynamic_stats,omitempty"`
}
// HTTPFilter definition
type HTTPFilter struct {
Type string `json:"type"`
Name string `json:"name"`
Config interface{} `json:"config"`
}
// Runtime definition
type Runtime struct {
Key string `json:"key"`
Default int `json:"default"`
}
// Decorator definition
type Decorator struct {
Operation string `json:"operation"`
}
// HTTPRoute definition
type HTTPRoute struct {
Runtime *Runtime `json:"runtime,omitempty"`
Path string `json:"path,omitempty"`
Prefix string `json:"prefix,omitempty"`
Regex string `json:"regex,omitempty"`
PrefixRewrite string `json:"prefix_rewrite,omitempty"`
HostRewrite string `json:"host_rewrite,omitempty"`
PathRedirect string `json:"path_redirect,omitempty"`
HostRedirect string `json:"host_redirect,omitempty"`
Cluster string `json:"cluster,omitempty"`
WeightedClusters *WeightedCluster `json:"weighted_clusters,omitempty"`
Headers Headers `json:"headers,omitempty"`
TimeoutMS int64 `json:"timeout_ms"`
RetryPolicy *RetryPolicy `json:"retry_policy,omitempty"`
OpaqueConfig map[string]string `json:"opaque_config,omitempty"`
AutoHostRewrite bool `json:"auto_host_rewrite,omitempty"`
WebsocketUpgrade bool `json:"use_websocket,omitempty"`
ShadowCluster *ShadowCluster `json:"shadow,omitempty"`
HeadersToAdd []AppendedHeader `json:"request_headers_to_add,omitempty"`
CORSPolicy *CORSPolicy `json:"cors,omitempty"`
Decorator *Decorator `json:"decorator,omitempty"`
// clusters contains the set of referenced clusters in the route; the field is special
// and used only to aggregate cluster information after composing routes
Clusters Clusters `json:"-"`
// faults contains the set of referenced faults in the route; the field is special
// and used only to aggregate fault filter information after composing routes
faults []*HTTPFilter
}
// Redirect returns true if route contains redirect logic
func (route *HTTPRoute) Redirect() bool {
return route.HostRedirect != "" || route.PathRedirect != ""
}
// CatchAll returns true if the route matches all requests
func (route *HTTPRoute) CatchAll() bool {
return len(route.Headers) == 0 && route.Path == "" && route.Prefix == "/"
}
//BasicHash returns hash string by route path\prefix\header
func (route *HTTPRoute) BasicHash() string {
key := sha256.New()
var header string
sort.Sort(route.Headers)
for _, h := range route.Headers {
header += h.Name + h.Value
}
key.Write([]byte(route.Path + route.Prefix + header))
return string(key.Sum(nil))
}
// CombinePathPrefix checks that the route applies for a given path and prefix
// match and updates the path and the prefix in the route. If the route is
// incompatible with the path or the prefix, returns nil. Either path or
// prefix must be set but not both. The resulting route must match exactly the
// requests that match both the original route and the supplied path and
// prefix.
func (route *HTTPRoute) CombinePathPrefix(path, prefix string) *HTTPRoute {
switch {
case path == "" && route.Path == "" && strings.HasPrefix(route.Prefix, prefix):
// pick the longest prefix if both are prefix matches
return route
case path == "" && route.Path == "" && strings.HasPrefix(prefix, route.Prefix):
route.Prefix = prefix
return route
case prefix == "" && route.Prefix == "" && route.Path == path:
// pick only if path matches if both are path matches
return route
case path == "" && route.Prefix == "" && strings.HasPrefix(route.Path, prefix):
// if mixed, pick if route path satisfies the prefix
return route
case prefix == "" && route.Path == "" && strings.HasPrefix(path, route.Prefix):
// if mixed, pick if route prefix satisfies the path and change route to path
route.Path = path
route.Prefix = ""
return route
default:
return nil
}
}
// CORSPolicy definition
// See: https://www.envoyproxy.io/envoy/configuration/http_filters/cors_filter.html#config-http-filters-cors
type CORSPolicy struct {
Enabled bool `json:"enabled,omitempty"`
AllowCredentials bool `json:"allow_credentials,omitempty"`
AllowMethods string `json:"allow_methods,omitempty"`
AllowHeaders string `json:"allow_headers,omitempty"`
ExposeHeaders string `json:"expose_headers,omitempty"`
MaxAge int `json:"max_age,string,omitempty"`
AllowOrigin []string `json:"allow_origin,omitempty"`
}
// RetryPolicy definition
// See: https://lyft.github.io/envoy/docs/configuration/http_conn_man/route_config/route.html#retry-policy
type RetryPolicy struct {
Policy string `json:"retry_on"` //if unset, set to 5xx,connect-failure,refused-stream
NumRetries int `json:"num_retries,omitempty"`
PerTryTimeoutMS int64 `json:"per_try_timeout_ms,omitempty"`
}
// ShadowCluster definition
// See: https://www.envoyproxy.io/envoy/configuration/http_conn_man/route_config/route.html?
// highlight=shadow#config-http-conn-man-route-table-route-shadow
type ShadowCluster struct {
Cluster string `json:"cluster"`
}
// WeightedCluster definition
// See https://envoyproxy.github.io/envoy/configuration/http_conn_man/route_config/route.html
type WeightedCluster struct {
Clusters []*WeightedClusterEntry `json:"clusters"`
RuntimeKeyPrefix string `json:"runtime_key_prefix,omitempty"`
}
// WeightedClusterEntry definition. Describes the format of each entry in the WeightedCluster
type WeightedClusterEntry struct {
Name string `json:"name"`
Weight int `json:"weight"`
}
// VirtualHost definition
type VirtualHost struct {
Name string `json:"name"`
Domains []string `json:"domains"`
Routes []*HTTPRoute `json:"routes"`
}
func (host *VirtualHost) clusters() Clusters {
out := make(Clusters, 0)
for _, route := range host.Routes {
out = append(out, route.Clusters...)
}
return out
}
//UniqVirtualHost according to the rules of VirtualHost in http route
//merge the VirtualHost that have same domain
//if have same domain, prifix, path and header,support weight
func UniqVirtualHost(vhs []*VirtualHost) (revhs []*VirtualHost) {
var domains = make(map[string]*VirtualHost, 0)
for _, vh := range vhs {
for _, domain := range vh.Domains {
if cahcevh, ok := domains[domain]; ok {
cahcevh.Routes = append(cahcevh.Routes, vh.Routes...)
} else {
domains[domain] = vh
}
}
}
for _, v := range domains {
//supprot weight if have same prifix, path and header
var keys = make(map[string]*HTTPRoute, 0)
for _, route := range v.Routes {
key := route.BasicHash()
if cacheroute, ok := keys[key]; ok {
cacheroute.WeightedClusters.Clusters = append(cacheroute.WeightedClusters.Clusters, route.WeightedClusters.Clusters...)
} else {
keys[key] = route
}
}
var routes []*HTTPRoute
for _, v := range keys {
var total int
var i int
for i = 0; i < len(v.WeightedClusters.Clusters)-1; i++ {
total += v.WeightedClusters.Clusters[i].Weight
if total >= 100 {
break
}
}
if total > 100 {
v.WeightedClusters.Clusters[i].Weight = v.WeightedClusters.Clusters[i].Weight - (total - 100)
if i+1 < len(v.WeightedClusters.Clusters) {
for j := i + 1; j < len(v.WeightedClusters.Clusters); j++ {
v.WeightedClusters.Clusters[j].Weight = 0
}
}
}
if total == 100 && i+1 < len(v.WeightedClusters.Clusters) {
for j := i + 1; j < len(v.WeightedClusters.Clusters); j++ {
v.WeightedClusters.Clusters[j].Weight = 0
}
}
if total < 100 {
v.WeightedClusters.Clusters[i].Weight = 100 - total
}
routes = append(routes, v)
}
v.Routes = routes
revhs = append(revhs, v)
}
return
}
// HTTPRouteConfig definition
type HTTPRouteConfig struct {
ValidateClusters bool `json:"validate_clusters"`
VirtualHosts []*VirtualHost `json:"virtual_hosts"`
}
// HTTPRouteConfigs is a map from the port number to the route config
type HTTPRouteConfigs map[int]*HTTPRouteConfig
// EnsurePort creates a route config if necessary
func (routes HTTPRouteConfigs) EnsurePort(port int) *HTTPRouteConfig {
config, ok := routes[port]
if !ok {
config = &HTTPRouteConfig{ValidateClusters: ValidateClusters}
routes[port] = config
}
return config
}
// Clusters returns the clusters corresponding to the given routes.
func (routes HTTPRouteConfigs) Clusters() Clusters {
out := make(Clusters, 0)
for _, config := range routes {
out = append(out, config.Clusters()...)
}
return out
}
// Normalize normalizes the route configs.
func (routes HTTPRouteConfigs) Normalize() HTTPRouteConfigs {
out := make(HTTPRouteConfigs)
// sort HTTP routes by virtual hosts, rest should be deterministic
for port, routeConfig := range routes {
out[port] = routeConfig.Normalize()
}
return out
}
// Combine creates a new route config that is the union of all HTTP routes.
// note that the virtual hosts without an explicit port suffix (IP:PORT) are stripped
// for all routes except the route for port 80.
func (routes HTTPRouteConfigs) Combine() *HTTPRouteConfig {
out := &HTTPRouteConfig{ValidateClusters: ValidateClusters}
for port, config := range routes {
for _, host := range config.VirtualHosts {
vhost := &VirtualHost{
Name: host.Name,
Routes: host.Routes,
}
for _, domain := range host.Domains {
if port == 80 || strings.Contains(domain, ":") {
vhost.Domains = append(vhost.Domains, domain)
}
}
if len(vhost.Domains) > 0 {
out.VirtualHosts = append(out.VirtualHosts, vhost)
}
}
}
return out.Normalize()
}
// faults aggregates fault filters across virtual hosts in single http_conn_man
func (rc *HTTPRouteConfig) faults() []*HTTPFilter {
out := make([]*HTTPFilter, 0)
for _, host := range rc.VirtualHosts {
for _, route := range host.Routes {
out = append(out, route.faults...)
}
}
return out
}
// Clusters returns the clusters for the given route config.
func (rc *HTTPRouteConfig) Clusters() Clusters {
out := make(Clusters, 0)
for _, host := range rc.VirtualHosts {
out = append(out, host.clusters()...)
}
return out
}
// Normalize normalizes the route config.
func (rc *HTTPRouteConfig) Normalize() *HTTPRouteConfig {
hosts := make([]*VirtualHost, len(rc.VirtualHosts))
copy(hosts, rc.VirtualHosts)
sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name })
return &HTTPRouteConfig{ValidateClusters: ValidateClusters, VirtualHosts: hosts}
}
// AccessLog definition.
type AccessLog struct {
Path string `json:"path"`
Format string `json:"format,omitempty"`
Filter string `json:"filter,omitempty"`
}
// HTTPFilterConfig definition
type HTTPFilterConfig struct {
CodecType string `json:"codec_type"`
StatPrefix string `json:"stat_prefix"`
GenerateRequestID bool `json:"generate_request_id,omitempty"`
UseRemoteAddress bool `json:"use_remote_address,omitempty"`
Tracing *HTTPFilterTraceConfig `json:"tracing,omitempty"`
RouteConfig *HTTPRouteConfig `json:"route_config,omitempty"`
RDS *RDS `json:"rds,omitempty"`
Filters []HTTPFilter `json:"filters"`
AccessLog []AccessLog `json:"access_log,omitempty"`
}
// IsNetworkFilterConfig marks HTTPFilterConfig as an implementation of NetworkFilterConfig
func (*HTTPFilterConfig) IsNetworkFilterConfig() {}
// HTTPFilterTraceConfig definition
type HTTPFilterTraceConfig struct {
OperationName string `json:"operation_name"`
}
// TCPRoute definition
type TCPRoute struct {
Cluster string `json:"cluster"`
DestinationIPList []string `json:"destination_ip_list,omitempty"`
DestinationPorts string `json:"destination_ports,omitempty"`
SourceIPList []string `json:"source_ip_list,omitempty"`
SourcePorts string `json:"source_ports,omitempty"`
// special value to retain dependent cluster definition for TCP routes.
clusterRef *Cluster
}
// TCPRouteByRoute sorts TCP routes over all route sub fields.
type TCPRouteByRoute []*TCPRoute
func (r TCPRouteByRoute) Len() int {
return len(r)
}
func (r TCPRouteByRoute) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r TCPRouteByRoute) Less(i, j int) bool {
if r[i].Cluster != r[j].Cluster {
return r[i].Cluster < r[j].Cluster
}
compare := func(a, b []string) bool {
lenA, lenB := len(a), len(b)
min := lenA
if min > lenB {
min = lenB
}
for k := 0; k < min; k++ {
if a[k] != b[k] {
return a[k] < b[k]
}
}
return lenA < lenB
}
if less := compare(r[i].DestinationIPList, r[j].DestinationIPList); less {
return less
}
if r[i].DestinationPorts != r[j].DestinationPorts {
return r[i].DestinationPorts < r[j].DestinationPorts
}
if less := compare(r[i].SourceIPList, r[j].SourceIPList); less {
return less
}
if r[i].SourcePorts != r[j].SourcePorts {
return r[i].SourcePorts < r[j].SourcePorts
}
return false
}
// TCPProxyFilterConfig definition
type TCPProxyFilterConfig struct {
StatPrefix string `json:"stat_prefix"`
RouteConfig *TCPRouteConfig `json:"route_config"`
}
// IsNetworkFilterConfig marks TCPProxyFilterConfig as an implementation of NetworkFilterConfig
func (*TCPProxyFilterConfig) IsNetworkFilterConfig() {}
// TCPRouteConfig (or generalize as RouteConfig or L4RouteConfig for TCP/UDP?)
type TCPRouteConfig struct {
Routes []*TCPRoute `json:"routes"`
}
// MongoProxyFilterConfig definition
type MongoProxyFilterConfig struct {
StatPrefix string `json:"stat_prefix"`
}
// IsNetworkFilterConfig marks MongoProxyFilterConfig as an implementation of NetworkFilterConfig
func (*MongoProxyFilterConfig) IsNetworkFilterConfig() {}
// CORSFilterConfig definition
// See: https://www.envoyproxy.io/envoy/configuration/http_filters/cors_filter.html#config-http-filters-cors
type CORSFilterConfig struct{}
// IsNetworkFilterConfig marks CORSFilterConfig as an implementation of NetworkFilterConfig
func (*CORSFilterConfig) IsNetworkFilterConfig() {}
// RedisConnPool definition
type RedisConnPool struct {
OperationTimeoutMS int64 `json:"op_timeout_ms"`
}
// RedisProxyFilterConfig definition
type RedisProxyFilterConfig struct {
ClusterName string `json:"cluster_name"`
ConnPool *RedisConnPool `json:"conn_pool"`
StatPrefix string `json:"stat_prefix"`
}
// IsNetworkFilterConfig marks RedisProxyFilterConfig as an implementation of NetworkFilterConfig
func (*RedisProxyFilterConfig) IsNetworkFilterConfig() {}
// NetworkFilter definition
type NetworkFilter struct {
Type string `json:"-"`
Name string `json:"name"`
Config NetworkFilterConfig `json:"config"`
}
// NetworkFilterConfig is a marker interface
type NetworkFilterConfig interface {
IsNetworkFilterConfig()
}
// NetworkFilterTypes maps filter names to types of structs that implement them. It is used when unmarshaling JSON data.
// To add your own NetworkFilter types, add additional entries to this map prior to calling json.Unmarshal.
var NetworkFilterTypes = map[string]reflect.Type{
RedisProxyFilter: reflect.TypeOf(RedisProxyFilterConfig{}),
CORSFilter: reflect.TypeOf(CORSFilterConfig{}),
MongoProxyFilter: reflect.TypeOf(MongoProxyFilterConfig{}),
TCPProxyFilter: reflect.TypeOf(TCPProxyFilterConfig{}),
HTTPConnectionManager: reflect.TypeOf(HTTPFilterConfig{}),
MixerFilter: reflect.TypeOf(FilterMixerConfig{}),
}
// UnmarshalJSON handles custom unmarshal logic for the NetworkFilter struct. This is needed because the config field
// depends on the filter name.
func (nf *NetworkFilter) UnmarshalJSON(b []byte) error {
// First, unmarshal to a generic data structure so we can get the name.
var j interface{}
err := json.Unmarshal(b, &j)
if err != nil {
return err
}
m := j.(map[string]interface{})
n, ok := m["name"].(string)
if !ok {
return errors.New("filter missing name field")
}
// Once we have the name, we can look up the concrete type of the config field.
t, ok := NetworkFilterTypes[n]
if !ok {
return fmt.Errorf("unknown filter name: %s", n)
}
v := reflect.New(t)
// Since Unmarshal takes a []byte, we re-marshall the config and then call Unmarshal on it.
cfgBytes, err := json.Marshal(m["config"])
if err != nil {
return err
}
err = json.Unmarshal(cfgBytes, v.Interface())
if err != nil {
return err
}
// Fill in the NetworkFilter
nf.Name = n
nf.Type = m["type"].(string)
nf.Config = v.Interface().(NetworkFilterConfig)
return nil
}
// Listener definition
type Listener struct {
Address string `json:"address"`
Name string `json:"name,omitempty"`
Filters []*NetworkFilter `json:"filters"`
SSLContext *SSLContext `json:"ssl_context,omitempty"`
BindToPort bool `json:"bind_to_port"`
UseOriginalDst bool `json:"use_original_dst,omitempty"`
}
// Listeners is a collection of listeners
type Listeners []*Listener
//Append append some listeners
func (l *Listeners) Append(new Listeners) {
*l = append(*l, new...)
}
//CreateHTTPCommonListener create simple http common listener
//listen port 80
func CreateHTTPCommonListener(name string, vh ...*VirtualHost) *Listener {
rcg := &HTTPRouteConfig{
VirtualHosts: vh,
}
hsf := HTTPFilter{
Type: "decoder",
Name: "router",
Config: make(map[string]string),
}
lhc := &HTTPFilterConfig{
CodecType: "auto",
StatPrefix: "ingress_http",
RouteConfig: rcg,
Filters: []HTTPFilter{hsf},
}
lfs := &NetworkFilter{
Name: "http_connection_manager",
Config: lhc,
}
plds := &Listener{
Name: name,
Address: fmt.Sprintf("tcp://127.0.0.1:%d", 80),
Filters: []*NetworkFilter{lfs},
BindToPort: true,
}
return plds
}
//CreateTCPCommonListener create tcp simple common listener
//listen the specified port
//associate the specified cluster.
func CreateTCPCommonListener(clusterName string, address string) *Listener {
ptr := &TCPRoute{
Cluster: clusterName,
}
lrs := &TCPRouteConfig{
Routes: []*TCPRoute{ptr},
}
lcg := &TCPProxyFilterConfig{
StatPrefix: clusterName,
RouteConfig: lrs,
}
lfs := &NetworkFilter{
Name: "tcp_proxy",
Config: lcg,
}
plds := &Listener{
Name: clusterName,
Address: address,
Filters: []*NetworkFilter{lfs},
BindToPort: true,
}
return plds
}
// Normalize sorts and de-duplicates listeners by address
func (listeners Listeners) normalize() Listeners {
out := make(Listeners, 0, len(listeners))
set := make(map[string]*Listener)
for _, listener := range listeners {
if l, collision := set[listener.Address]; collision {
ol, _ := json.Marshal(*l)
ll, _ := json.Marshal(*listener)
logrus.Errorf("Listener collision for %s\n---\n%s\n--- rejected ---\n%s", listener.Address,
string(ol), string(ll))
continue
}
out = append(out, listener)
set[listener.Address] = listener
}
sort.Slice(out, func(i, j int) bool { return out[i].Address < out[j].Address })
return out
}
// GetByAddress returns a listener by its address
func (listeners Listeners) GetByAddress(addr string) *Listener {
for _, listener := range listeners {
if listener.Address == addr {
return listener
}
}
return nil
}
// SSLContext definition
type SSLContext struct {
CertChainFile string `json:"cert_chain_file"`
PrivateKeyFile string `json:"private_key_file"`
CaCertFile string `json:"ca_cert_file,omitempty"`
RequireClientCertificate bool `json:"require_client_certificate"`
ALPNProtocols string `json:"alpn_protocols,omitempty"`
}
// SSLContextExternal definition
type SSLContextExternal struct {
CaCertFile string `json:"ca_cert_file,omitempty"`
}
// SSLContextWithSAN definition, VerifySubjectAltName cannot be nil.
type SSLContextWithSAN struct {
CertChainFile string `json:"cert_chain_file"`
PrivateKeyFile string `json:"private_key_file"`
CaCertFile string `json:"ca_cert_file,omitempty"`
VerifySubjectAltName []string `json:"verify_subject_alt_name"`
}
// Admin definition
type Admin struct {
AccessLogPath string `json:"access_log_path"`
Address string `json:"address"`
}
// Host definition
type Host struct {
URL string `json:"url"`
}
// Cluster definition
type Cluster struct {
Name string `json:"name"`
ServiceName string `json:"service_name,omitempty"`
ConnectTimeoutMs int64 `json:"connect_timeout_ms"`
Type string `json:"type"`
LbType string `json:"lb_type"`
MaxRequestsPerConnection int `json:"max_requests_per_connection,omitempty"`
Hosts []Host `json:"hosts,omitempty"`
SSLContext interface{} `json:"ssl_context,omitempty"`
Features string `json:"features,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuit_breakers,omitempty"`
OutlierDetection *OutlierDetection `json:"outlier_detection,omitempty"`
}
// CircuitBreaker definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_circuit_breakers.html#circuit-breakers
type CircuitBreaker struct {
Default DefaultCBPriority `json:"default"`
}
// DefaultCBPriority defines the circuit breaker for default cluster priority
type DefaultCBPriority struct {
MaxConnections int `json:"max_connections"`
MaxPendingRequests int `json:"max_pending_requests"`
MaxRequests int `json:"max_requests"`
MaxRetries int `json:"max_retries"`
}
// OutlierDetection definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_runtime.html#outlier-detection
type OutlierDetection struct {
ConsecutiveErrors int `json:"consecutive_5xx,omitempty"`
IntervalMS int64 `json:"interval_ms,omitempty"`
BaseEjectionTimeMS int64 `json:"base_ejection_time_ms,omitempty"`
MaxEjectionPercent int `json:"max_ejection_percent,omitempty"`
}
// Clusters is a collection of clusters
type Clusters []*Cluster
//Append append some clusters
func (c *Clusters) Append(new Clusters) {
*c = append(*c, new...)
}
// Normalize deduplicates and sorts clusters by name
func (c Clusters) Normalize() Clusters {
out := make(Clusters, 0, len(c))
set := make(map[string]bool)
for _, cluster := range c {
if !set[cluster.Name] {
set[cluster.Name] = true
out = append(out, cluster)
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out
}
// RoutesByPath sorts routes by their path and/or prefix, such that:
// - Exact path routes are "less than" than prefix path routes
// - Exact path routes are sorted lexicographically
// - Prefix path routes are sorted anti-lexicographically
//
// This order ensures that prefix path routes do not shadow more
// specific routes which share the same prefix.
type RoutesByPath []*HTTPRoute
func (r RoutesByPath) Len() int {
return len(r)
}
func (r RoutesByPath) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r RoutesByPath) Less(i, j int) bool {
if r[i].Path != "" {
if r[j].Path != "" {
// i and j are both path
return r[i].Path < r[j].Path
}
// i is path and j is prefix => i is "less than" j
return true
}
if r[j].Path != "" {
// i is prefix nad j is path => j is "less than" i
return false
}
// i and j are both prefix
return r[i].Prefix > r[j].Prefix
}
// Headers sorts headers
type Headers []Header
func (s Headers) Len() int {
return len(s)
}
func (s Headers) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s Headers) Less(i, j int) bool {
if s[i].Name == s[j].Name {
if s[i].Regex == s[j].Regex {
return s[i].Value < s[j].Value
}
// true is less, false is more
return s[i].Regex
}
return s[i].Name < s[j].Name
}
// DiscoveryCluster is a service discovery service definition
type DiscoveryCluster struct {
Cluster *Cluster `json:"cluster"`
RefreshDelayMs int64 `json:"refresh_delay_ms"`
}
// LDSCluster is a reference to LDS cluster by name
type LDSCluster struct {
Cluster string `json:"cluster"`
RefreshDelayMs int64 `json:"refresh_delay_ms"`
}
// CDSCluter is result struct for cds api
type CDSCluter struct {
Clusters Clusters `json:"clusters"`
}
// DiscoverHost is hosts that make up the service.
type DiscoverHost struct {
Address string `json:"ip_address"`
Port int `json:"port"`
Tags *Tags `json:"tags,omitempty"`
}
// Tags is Discover host tags
type Tags struct {
AZ string `json:"az,omitempty"`
Canary bool `json:"canary,omitempty"`
// Weight is an integer in the range [1, 100] or empty
Weight int `json:"load_balancing_weight,omitempty"`
}
//DiscoverHosts is a collection of DiscoverHost
type DiscoverHosts []*DiscoverHost
//SDSHost is result struct for sds api
type SDSHost struct {
Hosts DiscoverHosts `json:"hosts"`
}
// LDSListener is result struct for lds api
type LDSListener struct {
Listeners Listeners `json:"listeners"`
}
// RDS definition
type RDS struct {
Cluster string `json:"cluster"`
RouteConfigName string `json:"route_config_name"`
RefreshDelayMs int64 `json:"refresh_delay_ms"`
}
// ClusterManager definition
type ClusterManager struct {
Clusters Clusters `json:"clusters"`
SDS *DiscoveryCluster `json:"sds,omitempty"`
CDS *DiscoveryCluster `json:"cds,omitempty"`
LocalClusterName string `json:"local_cluster_name,omitempty"`
}
// FilterMixerConfig definition.
//
// NOTE: all fields marked as DEPRECATED are part of the original v1
// mixerclient configuration. They are deprecated and will be
// eventually removed once proxies are updated.
//
// Going forwards all mixerclient configuration should represeted by
// istio.io/api/mixer/v1/config/client/mixer_filter_config.proto and
// encoded in the `V2` field below.
//
type FilterMixerConfig struct {
// DEPRECATED: MixerAttributes specifies the static list of attributes that are sent with
// each request to Mixer.
MixerAttributes map[string]string `json:"mixer_attributes,omitempty"`
// DEPRECATED: ForwardAttributes specifies the list of attribute keys and values that
// are forwarded as an HTTP header to the server side proxy
ForwardAttributes map[string]string `json:"forward_attributes,omitempty"`
// DEPRECATED: QuotaName specifies the name of the quota bucket to withdraw tokens from;
// an empty name means no quota will be charged.
QuotaName string `json:"quota_name,omitempty"`
// DEPRECATED: If set to true, disables mixer check calls for TCP connections
DisableTCPCheckCalls bool `json:"disable_tcp_check_calls,omitempty"`
// istio.io/api/mixer/v1/config/client configuration protobuf
// encoded as a generic map using canonical JSON encoding.
//
// If `V2` field is not empty, the DEPRECATED fields above should
// be discarded.
V2 map[string]interface{} `json:"v2,omitempty"`
}
// IsNetworkFilterConfig marks FilterMixerConfig as an implementation of NetworkFilterConfig
func (*FilterMixerConfig) IsNetworkFilterConfig() {}