connect: update centralized upstreams representation in service-defaults (#10015)

This commit is contained in:
R.B. Boyer 2021-04-15 14:21:44 -05:00 committed by GitHub
parent f33feeeec4
commit 4db8b78854
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 612 additions and 231 deletions

View File

@ -4,14 +4,15 @@ import (
"fmt"
"time"
"github.com/armon/go-metrics/prometheus"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics/prometheus"
"github.com/hashicorp/go-hclog"
memdb "github.com/hashicorp/go-memdb"
"github.com/mitchellh/copystructure"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs"
memdb "github.com/hashicorp/go-memdb"
"github.com/mitchellh/copystructure"
)
var ConfigSummaries = []prometheus.SummaryDefinition{
@ -43,7 +44,8 @@ var ConfigSummaries = []prometheus.SummaryDefinition{
// The ConfigEntry endpoint is used to query centralized config information
type ConfigEntry struct {
srv *Server
srv *Server
logger hclog.Logger
}
// Apply does an upsert of the given config entry.
@ -449,23 +451,30 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
}
}
// Then store upstreams inferred from service-defaults
if serviceConf != nil && serviceConf.Connect != nil {
for sid := range serviceConf.Connect.UpstreamConfigs {
seenUpstreams[structs.ServiceIDFromString(sid)] = struct{}{}
}
}
// usConfigs stores the opaque config map for each upstream and is keyed on the upstream's ID.
usConfigs := make(map[structs.ServiceID]map[string]interface{})
// Then store upstreams inferred from service-defaults and mapify the overrides.
var (
upstreamConfigs = make(map[structs.ServiceID]*structs.UpstreamConfig)
upstreamDefaults *structs.UpstreamConfig
upstreamConfigs map[string]*structs.UpstreamConfig
// usConfigs stores the opaque config map for each upstream and is keyed on the upstream's ID.
usConfigs = make(map[structs.ServiceID]map[string]interface{})
)
if serviceConf != nil && serviceConf.Connect != nil {
if serviceConf.Connect.UpstreamDefaults != nil {
upstreamDefaults = serviceConf.Connect.UpstreamDefaults
if serviceConf != nil && serviceConf.UpstreamConfig != nil {
for i, override := range serviceConf.UpstreamConfig.Overrides {
if override.Name == "" {
c.logger.Warn(
"Skipping UpstreamConfig.Overrides entry without a required name field",
"entryIndex", i,
"kind", serviceConf.GetKind(),
"name", serviceConf.GetName(),
"namespace", serviceConf.GetEnterpriseMeta().NamespaceOrEmpty(),
)
continue // skip this impossible condition
}
seenUpstreams[override.ServiceID()] = struct{}{}
upstreamConfigs[override.ServiceID()] = override
}
if serviceConf.UpstreamConfig.Defaults != nil {
upstreamDefaults = serviceConf.UpstreamConfig.Defaults
// Store the upstream defaults under a wildcard key so that they can be applied to
// upstreams that are inferred from intentions and do not have explicit upstream configuration.
@ -475,9 +484,6 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
wildcard := structs.NewServiceID(structs.WildcardSpecifier, structs.WildcardEnterpriseMeta())
usConfigs[wildcard] = cfgMap
}
if serviceConf.Connect.UpstreamConfigs != nil {
upstreamConfigs = serviceConf.Connect.UpstreamConfigs
}
}
for upstream := range seenUpstreams {
@ -486,7 +492,7 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
// The protocol of an upstream is resolved in this order:
// 1. Default protocol from proxy-defaults (how all services should be addressed)
// 2. Protocol for upstream service defined in its service-defaults (how the upstream wants to be addressed)
// 3. Protocol defined for the upstream in the service-defaults.(upstream_defaults|upstream_configs) of the downstream
// 3. Protocol defined for the upstream in the service-defaults.(upstream_config.defaults|upstream_config.overrides) of the downstream
// (how the downstream wants to address it)
protocol := proxyConfGlobalProtocol
@ -518,14 +524,14 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
// The goal is to flatten the mesh gateway mode in this order:
// 0. Value from centralized upstream_defaults
// 1. Value from local proxy registration
// 2. Value from centralized upstream_configs
// 2. Value from centralized upstream_config
// 3. Value from local upstream definition. This last step is done in the client's service manager.
if !args.MeshGateway.IsZero() {
resolvedCfg["mesh_gateway"] = args.MeshGateway
}
if upstreamConfigs[upstream.String()] != nil {
upstreamConfigs[upstream.String()].MergeInto(resolvedCfg)
if upstreamConfigs[upstream] != nil {
upstreamConfigs[upstream].MergeInto(resolvedCfg)
}
if len(resolvedCfg) > 0 {

View File

@ -1029,9 +1029,10 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamConfigs: map[string]*structs.UpstreamConfig{
mysql.String(): {
UpstreamConfig: &structs.UpstreamConfiguration{
Overrides: []*structs.UpstreamConfig{
{
Name: "mysql",
Protocol: "http",
},
},
@ -1070,9 +1071,10 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamConfigs: map[string]*structs.UpstreamConfig{
mysql.String(): {
UpstreamConfig: &structs.UpstreamConfiguration{
Overrides: []*structs.UpstreamConfig{
{
Name: "mysql",
Protocol: "http",
},
},
@ -1115,8 +1117,8 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamDefaults: &structs.UpstreamConfig{
UpstreamConfig: &structs.UpstreamConfiguration{
Defaults: &structs.UpstreamConfig{
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
},
},
@ -1154,7 +1156,7 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
},
},
{
name: "upstream_configs overrides all",
name: "upstream_config.overrides override all",
entries: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
@ -1171,8 +1173,8 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamDefaults: &structs.UpstreamConfig{
UpstreamConfig: &structs.UpstreamConfiguration{
Defaults: &structs.UpstreamConfig{
Protocol: "http",
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
PassiveHealthCheck: &structs.PassiveHealthCheck{
@ -1180,8 +1182,9 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
MaxFailures: 2,
},
},
UpstreamConfigs: map[string]*structs.UpstreamConfig{
mysql.String(): {
Overrides: []*structs.UpstreamConfig{
{
Name: "mysql",
Protocol: "grpc",
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal},
},
@ -1239,12 +1242,13 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamDefaults: &structs.UpstreamConfig{
UpstreamConfig: &structs.UpstreamConfiguration{
Defaults: &structs.UpstreamConfig{
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
},
UpstreamConfigs: map[string]*structs.UpstreamConfig{
mysql.String(): {
Overrides: []*structs.UpstreamConfig{
{
Name: "mysql",
Protocol: "grpc",
},
},
@ -1286,12 +1290,13 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamDefaults: &structs.UpstreamConfig{
UpstreamConfig: &structs.UpstreamConfiguration{
Defaults: &structs.UpstreamConfig{
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
},
UpstreamConfigs: map[string]*structs.UpstreamConfig{
mysql.String(): {
Overrides: []*structs.UpstreamConfig{
{
Name: "mysql",
Protocol: "grpc",
},
},
@ -1338,12 +1343,13 @@ func TestConfigEntry_ResolveServiceConfig_Upstreams(t *testing.T) {
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "api",
Connect: &structs.ConnectConfiguration{
UpstreamDefaults: &structs.UpstreamConfig{
UpstreamConfig: &structs.UpstreamConfiguration{
Defaults: &structs.UpstreamConfig{
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
},
UpstreamConfigs: map[string]*structs.UpstreamConfig{
mysql.String(): {
Overrides: []*structs.UpstreamConfig{
{
Name: "mysql",
Protocol: "grpc",
},
},

View File

@ -6,7 +6,7 @@ func init() {
registerEndpoint(func(s *Server) interface{} { return &ACL{s, s.loggers.Named(logging.ACL)} })
registerEndpoint(func(s *Server) interface{} { return &Catalog{s, s.loggers.Named(logging.Catalog)} })
registerEndpoint(func(s *Server) interface{} { return NewCoordinate(s, s.logger) })
registerEndpoint(func(s *Server) interface{} { return &ConfigEntry{s} })
registerEndpoint(func(s *Server) interface{} { return &ConfigEntry{s, s.loggers.Named(logging.ConfigEntry)} })
registerEndpoint(func(s *Server) interface{} { return &ConnectCA{srv: s, logger: s.loggers.Named(logging.Connect)} })
registerEndpoint(func(s *Server) interface{} { return &FederationState{s} })
registerEndpoint(func(s *Server) interface{} { return &DiscoveryChain{s} })

View File

@ -89,10 +89,8 @@ type ServiceConfigEntry struct {
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"`
ExternalSNI string `json:",omitempty" alias:"external_sni"`
Connect *ConnectConfiguration `json:",omitempty"`
ExternalSNI string `json:",omitempty" alias:"external_sni"`
UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"`
Meta map[string]string `json:",omitempty"`
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
@ -102,6 +100,7 @@ type ServiceConfigEntry struct {
func (e *ServiceConfigEntry) Clone() *ServiceConfigEntry {
e2 := *e
e2.Expose = e.Expose.Clone()
e2.UpstreamConfig = e.UpstreamConfig.Clone()
return &e2
}
@ -131,20 +130,48 @@ func (e *ServiceConfigEntry) Normalize() error {
e.Kind = ServiceDefaults
e.Protocol = strings.ToLower(e.Protocol)
e.Connect.Normalize()
e.EnterpriseMeta.Normalize()
return nil
var validationErr error
if e.UpstreamConfig != nil {
for _, override := range e.UpstreamConfig.Overrides {
err := override.NormalizeWithName(&e.EnterpriseMeta)
if err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream override for %s: %v", override.ServiceName(), err))
}
}
if e.UpstreamConfig.Defaults != nil {
err := e.UpstreamConfig.Defaults.NormalizeWithoutName()
if err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream defaults: %v", err))
}
}
}
return validationErr
}
func (e *ServiceConfigEntry) Validate() error {
if e.Name == "" {
return fmt.Errorf("Name is required")
}
validationErr := validateConfigEntryMeta(e.Meta)
if e.Connect != nil {
err := e.Connect.Validate()
if err != nil {
validationErr = multierror.Append(validationErr, err)
if e.UpstreamConfig != nil {
for _, override := range e.UpstreamConfig.Overrides {
err := override.ValidateWithName()
if err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream override for %s: %v", override.ServiceName(), err))
}
}
if e.UpstreamConfig.Defaults != nil {
if err := e.UpstreamConfig.Defaults.ValidateWithoutName(); err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream defaults: %v", err))
}
}
}
@ -179,44 +206,32 @@ func (e *ServiceConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
return &e.EnterpriseMeta
}
type ConnectConfiguration struct {
// UpstreamConfigs is a map of <namespace/>service to per-upstream configuration
UpstreamConfigs map[string]*UpstreamConfig `json:",omitempty" alias:"upstream_configs"`
type UpstreamConfiguration struct {
// Overrides is a slice of per-service configuration. The name field is
// required.
Overrides []*UpstreamConfig `json:",omitempty"`
// UpstreamDefaults contains default configuration for all upstreams of a given service
UpstreamDefaults *UpstreamConfig `json:",omitempty" alias:"upstream_defaults"`
// Defaults contains default configuration for all upstreams of a given
// service. The name field must be empty.
Defaults *UpstreamConfig `json:",omitempty"`
}
func (cfg *ConnectConfiguration) Normalize() {
if cfg == nil {
return
}
for _, v := range cfg.UpstreamConfigs {
v.Normalize()
}
if cfg.UpstreamDefaults != nil {
cfg.UpstreamDefaults.Normalize()
}
}
func (cfg ConnectConfiguration) Validate() error {
var validationErr error
for k, v := range cfg.UpstreamConfigs {
if err := v.Validate(); err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream config for %s: %v", k, err))
func (c *UpstreamConfiguration) Clone() *UpstreamConfiguration {
var c2 UpstreamConfiguration
if len(c.Overrides) > 0 {
c2.Overrides = make([]*UpstreamConfig, 0, len(c.Overrides))
for _, o := range c.Overrides {
dup := o.Clone()
c2.Overrides = append(c2.Overrides, &dup)
}
}
if cfg.UpstreamDefaults != nil {
err := cfg.UpstreamDefaults.Validate()
if err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream defaults %v", err))
}
if c.Defaults != nil {
def2 := c.Defaults.Clone()
c2.Defaults = &def2
}
return validationErr
return &c2
}
// ProxyConfigEntry is the top-level struct for global proxy configuration defaults.
@ -651,6 +666,11 @@ func (r *ServiceConfigRequest) CacheInfo() cache.RequestInfo {
}
type UpstreamConfig struct {
// Name is only accepted within a service-defaults config entry.
Name string `json:",omitempty"`
// EnterpriseMeta is only accepted within a service-defaults config entry.
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
// EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's
// listener.
//
@ -688,6 +708,29 @@ type UpstreamConfig struct {
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway" `
}
func (cfg UpstreamConfig) Clone() UpstreamConfig {
cfg2 := cfg
cfg2.Limits = cfg.Limits.Clone()
cfg2.PassiveHealthCheck = cfg.PassiveHealthCheck.Clone()
return cfg2
}
func (cfg *UpstreamConfig) ServiceID() ServiceID {
if cfg.Name == "" {
return ServiceID{}
}
return NewServiceID(cfg.Name, &cfg.EnterpriseMeta)
}
func (cfg *UpstreamConfig) ServiceName() ServiceName {
if cfg.Name == "" {
return ServiceName{}
}
return NewServiceName(cfg.Name, &cfg.EnterpriseMeta)
}
func (cfg UpstreamConfig) MergeInto(dst map[string]interface{}) {
// Avoid storing empty values in the map, since these can act as overrides
if cfg.EnvoyListenerJSON != "" {
@ -713,15 +756,48 @@ func (cfg UpstreamConfig) MergeInto(dst map[string]interface{}) {
}
}
func (cfg *UpstreamConfig) Normalize() {
func (cfg *UpstreamConfig) NormalizeWithoutName() error {
return cfg.normalize(false, nil)
}
func (cfg *UpstreamConfig) NormalizeWithName(entMeta *EnterpriseMeta) error {
return cfg.normalize(true, entMeta)
}
func (cfg *UpstreamConfig) normalize(named bool, entMeta *EnterpriseMeta) error {
if named {
// If the upstream namespace is omitted it inherits that of the enclosing
// config entry.
cfg.EnterpriseMeta.MergeNoWildcard(entMeta)
cfg.EnterpriseMeta.Normalize()
}
cfg.Protocol = strings.ToLower(cfg.Protocol)
if cfg.ConnectTimeoutMs < 0 {
cfg.ConnectTimeoutMs = 0
}
return nil
}
func (cfg UpstreamConfig) Validate() error {
func (cfg UpstreamConfig) ValidateWithoutName() error {
return cfg.validate(false)
}
func (cfg UpstreamConfig) ValidateWithName() error {
return cfg.validate(true)
}
func (cfg UpstreamConfig) validate(named bool) error {
if named {
if cfg.Name == "" {
return fmt.Errorf("Name is required")
}
} else {
if cfg.Name != "" {
return fmt.Errorf("Name must be empty")
}
if cfg.EnterpriseMeta.NamespaceOrEmpty() != "" {
return fmt.Errorf("Namespace must be empty")
}
}
var validationErr error
if cfg.PassiveHealthCheck != nil {
@ -758,8 +834,11 @@ func ParseUpstreamConfigNoDefaults(m map[string]interface{}) (UpstreamConfig, er
return cfg, err
}
err = decoder.Decode(m)
cfg.Normalize()
if err := decoder.Decode(m); err != nil {
return cfg, err
}
err = cfg.NormalizeWithoutName()
return cfg, err
}
@ -791,6 +870,14 @@ type PassiveHealthCheck struct {
MaxFailures uint32 `json:",omitempty" alias:"max_failures"`
}
func (chk *PassiveHealthCheck) Clone() *PassiveHealthCheck {
if chk == nil {
return nil
}
chk2 := *chk
return &chk2
}
func (chk *PassiveHealthCheck) IsZero() bool {
zeroVal := PassiveHealthCheck{}
return *chk == zeroVal
@ -822,6 +909,25 @@ type UpstreamLimits struct {
MaxConcurrentRequests *int `json:",omitempty" alias:"max_concurrent_requests"`
}
func (ul *UpstreamLimits) Clone() *UpstreamLimits {
if ul == nil {
return nil
}
return &UpstreamLimits{
MaxConnections: intPointerCopy(ul.MaxConnections),
MaxPendingRequests: intPointerCopy(ul.MaxPendingRequests),
MaxConcurrentRequests: intPointerCopy(ul.MaxConcurrentRequests),
}
}
func intPointerCopy(v *int) *int {
if v == nil {
return nil
}
v2 := *v
return &v2
}
func (ul *UpstreamLimits) IsZero() bool {
zeroVal := UpstreamLimits{}
return *ul == zeroVal

View File

@ -10,6 +10,8 @@ import (
"github.com/hashicorp/hcl"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/sdk/testutil"
)
// TestDecodeConfigEntry is the 'structs' mirror image of
@ -113,22 +115,23 @@ func TestDecodeConfigEntry(t *testing.T) {
mesh_gateway {
mode = "remote"
}
connect {
upstream_configs {
redis {
upstream_config {
overrides = [
{
name = "redis"
passive_health_check {
interval = "2s"
max_failures = 3
}
}
"finance/billing" {
},
{
name = "finance--billing"
mesh_gateway {
mode = "remote"
}
}
}
upstream_defaults {
},
]
defaults {
connect_timeout_ms = 5
protocol = "http"
envoy_listener_json = "foo"
@ -153,22 +156,23 @@ func TestDecodeConfigEntry(t *testing.T) {
MeshGateway {
Mode = "remote"
}
Connect {
UpstreamConfigs {
"redis" {
UpstreamConfig {
Overrides = [
{
Name = "redis"
PassiveHealthCheck {
MaxFailures = 3
Interval = "2s"
}
}
"finance/billing" {
},
{
Name = "finance--billing"
MeshGateway {
Mode = "remote"
}
}
}
UpstreamDefaults {
},
]
Defaults {
EnvoyListenerJSON = "foo"
EnvoyClusterJSON = "bar"
ConnectTimeoutMs = 5
@ -193,19 +197,21 @@ func TestDecodeConfigEntry(t *testing.T) {
MeshGateway: MeshGatewayConfig{
Mode: MeshGatewayModeRemote,
},
Connect: &ConnectConfiguration{
UpstreamConfigs: map[string]*UpstreamConfig{
"redis": {
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "redis",
PassiveHealthCheck: &PassiveHealthCheck{
MaxFailures: 3,
Interval: 2 * time.Second,
},
},
"finance/billing": {
{
Name: "finance--billing",
MeshGateway: MeshGatewayConfig{Mode: MeshGatewayModeRemote},
},
},
UpstreamDefaults: &UpstreamConfig{
Defaults: &UpstreamConfig{
EnvoyListenerJSON: "foo",
EnvoyClusterJSON: "bar",
ConnectTimeoutMs: 5,
@ -1537,6 +1543,75 @@ func TestServiceConfigEntry_Normalize(t *testing.T) {
input ServiceConfigEntry
expect ServiceConfigEntry
}{
{
// This will do nothing to normalization, but it will fail at validation later
name: "upstream config override no name",
input: ServiceConfigEntry{
Name: "web",
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "good",
Protocol: "grpc",
},
{
Protocol: "http2",
},
{
Name: "also-good",
Protocol: "http",
},
},
},
},
expect: ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
EnterpriseMeta: *DefaultEnterpriseMeta(),
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "good",
EnterpriseMeta: *DefaultEnterpriseMeta(),
Protocol: "grpc",
},
{
EnterpriseMeta: *DefaultEnterpriseMeta(),
Protocol: "http2",
},
{
Name: "also-good",
EnterpriseMeta: *DefaultEnterpriseMeta(),
Protocol: "http",
},
},
},
},
},
{
// This will do nothing to normalization, but it will fail at validation later
name: "upstream config defaults with name",
input: ServiceConfigEntry{
Name: "web",
UpstreamConfig: &UpstreamConfiguration{
Defaults: &UpstreamConfig{
Name: "also-good",
Protocol: "http2",
},
},
},
expect: ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
EnterpriseMeta: *DefaultEnterpriseMeta(),
UpstreamConfig: &UpstreamConfiguration{
Defaults: &UpstreamConfig{
Name: "also-good",
Protocol: "http2",
},
},
},
},
{
name: "fill-in-kind",
input: ServiceConfigEntry{
@ -1567,33 +1642,39 @@ func TestServiceConfigEntry_Normalize(t *testing.T) {
input: ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
Connect: &ConnectConfiguration{
UpstreamConfigs: map[string]*UpstreamConfig{
"redis": {
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "redis",
Protocol: "TcP",
},
"memcached": {
{
Name: "memcached",
ConnectTimeoutMs: -1,
},
},
UpstreamDefaults: &UpstreamConfig{ConnectTimeoutMs: -20},
Defaults: &UpstreamConfig{ConnectTimeoutMs: -20},
},
EnterpriseMeta: *DefaultEnterpriseMeta(),
},
expect: ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
Connect: &ConnectConfiguration{
UpstreamConfigs: map[string]*UpstreamConfig{
"redis": {
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "redis",
EnterpriseMeta: *DefaultEnterpriseMeta(),
Protocol: "tcp",
ConnectTimeoutMs: 0,
},
"memcached": {
{
Name: "memcached",
EnterpriseMeta: *DefaultEnterpriseMeta(),
ConnectTimeoutMs: 0,
},
},
UpstreamDefaults: &UpstreamConfig{
Defaults: &UpstreamConfig{
ConnectTimeoutMs: 0,
},
},
@ -1611,6 +1692,108 @@ func TestServiceConfigEntry_Normalize(t *testing.T) {
}
}
func TestServiceConfigEntry_Validate(t *testing.T) {
tt := []struct {
name string
input *ServiceConfigEntry
expect *ServiceConfigEntry
expectErr string
}{
{
name: "upstream config override no name",
input: &ServiceConfigEntry{
Name: "web",
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "good",
Protocol: "grpc",
},
{
Protocol: "http2",
},
{
Name: "also-good",
Protocol: "http",
},
},
},
},
expectErr: `error in upstream override for : Name is required`,
},
{
name: "upstream config defaults with name",
input: &ServiceConfigEntry{
Name: "web",
UpstreamConfig: &UpstreamConfiguration{
Defaults: &UpstreamConfig{
Name: "also-good",
Protocol: "http2",
},
},
},
expectErr: `error in upstream defaults: Name must be empty`,
},
{
name: "connect-kitchen-sink",
input: &ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "redis",
Protocol: "TcP",
},
{
Name: "memcached",
ConnectTimeoutMs: -1,
},
},
Defaults: &UpstreamConfig{ConnectTimeoutMs: -20},
},
EnterpriseMeta: *DefaultEnterpriseMeta(),
},
expect: &ServiceConfigEntry{
Kind: ServiceDefaults,
Name: "web",
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "redis",
EnterpriseMeta: *DefaultEnterpriseMeta(),
Protocol: "tcp",
ConnectTimeoutMs: 0,
},
{
Name: "memcached",
EnterpriseMeta: *DefaultEnterpriseMeta(),
ConnectTimeoutMs: 0,
},
},
Defaults: &UpstreamConfig{ConnectTimeoutMs: 0},
},
EnterpriseMeta: *DefaultEnterpriseMeta(),
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
// normalize before validate since they always happen in that order
require.NoError(t, tc.input.Normalize())
err := tc.input.Validate()
if tc.expectErr != "" {
testutil.RequireErrorContains(t, err, tc.expectErr)
} else {
require.NoError(t, err)
require.Equal(t, tc.expect, tc.input)
}
})
}
}
func TestUpstreamConfig_MergeInto(t *testing.T) {
tt := []struct {
name string

View File

@ -383,6 +383,7 @@ func (u *Upstream) Validate() error {
if u.LocalBindPort == 0 && !u.CentrallyConfigured {
return fmt.Errorf("upstream local bind port cannot be zero")
}
return nil
}

View File

@ -9,11 +9,12 @@ import (
"sync"
"testing"
"github.com/hashicorp/consul/api"
bexpr "github.com/hashicorp/go-bexpr"
"github.com/mitchellh/pointerstructure"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/api"
)
var dumpFieldConfig = flag.Bool("dump-field-config", false, "generate field config dump file")

View File

@ -115,15 +115,22 @@ type ExposePath struct {
ParsedFromCheck bool
}
type ConnectConfiguration struct {
// UpstreamConfigs is a map of <namespace/>service to per-upstream configuration
UpstreamConfigs map[string]UpstreamConfig `json:",omitempty" alias:"upstream_configs"`
type UpstreamConfiguration struct {
// Overrides is a slice of per-service configuration. The name field is
// required.
Overrides []*UpstreamConfig `json:",omitempty"`
// UpstreamDefaults contains default configuration for all upstreams of a given service
UpstreamDefaults *UpstreamConfig `json:",omitempty" alias:"upstream_defaults"`
// Defaults contains default configuration for all upstreams of a given
// service. The name field must be empty.
Defaults *UpstreamConfig `json:",omitempty"`
}
type UpstreamConfig struct {
// Name is only accepted within a service-defaults config entry.
Name string `json:",omitempty"`
// Namespace is only accepted within a service-defaults config entry.
Namespace string `json:",omitempty"`
// EnvoyListenerJSON is a complete override ("escape hatch") for the upstream's
// listener.
//
@ -176,18 +183,18 @@ type PassiveHealthCheck struct {
type UpstreamLimits struct {
// MaxConnections is the maximum number of connections the local proxy can
// make to the upstream service.
MaxConnections int `alias:"max_connections"`
MaxConnections *int `alias:"max_connections"`
// MaxPendingRequests is the maximum number of requests that will be queued
// waiting for an available connection. This is mostly applicable to HTTP/1.1
// clusters since all HTTP/2 requests are streamed over a single
// connection.
MaxPendingRequests int `alias:"max_pending_requests"`
MaxPendingRequests *int `alias:"max_pending_requests"`
// MaxConcurrentRequests is the maximum number of in-flight requests that will be allowed
// to the upstream cluster at a point in time. This is mostly applicable to HTTP/2
// clusters since all HTTP/1.1 requests are limited by MaxConnections.
MaxConcurrentRequests int `alias:"max_concurrent_requests"`
MaxConcurrentRequests *int `alias:"max_concurrent_requests"`
}
type ServiceConfigEntry struct {
@ -198,12 +205,13 @@ type ServiceConfigEntry struct {
Mode ProxyMode `json:",omitempty"`
TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Connect *ConnectConfiguration `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
ExternalSNI string `json:",omitempty" alias:"external_sni"`
Meta map[string]string `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"`
Meta map[string]string `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
func (s *ServiceConfigEntry) GetKind() string {

View File

@ -343,21 +343,23 @@ func TestDecodeConfigEntry(t *testing.T) {
"TransparentProxy": {
"OutboundListenerPort": 808
},
"Connect": {
"UpstreamConfigs": {
"redis": {
"UpstreamConfig": {
"Overrides": [
{
"Name": "redis",
"PassiveHealthCheck": {
"MaxFailures": 3,
"Interval": "2s"
}
},
"finance/billing": {
{
"Name": "finance--billing",
"MeshGateway": {
"Mode": "remote"
}
}
},
"UpstreamDefaults": {
],
"Defaults": {
"EnvoyClusterJSON": "zip",
"EnvoyListenerJSON": "zop",
"ConnectTimeoutMs": 5000,
@ -389,27 +391,29 @@ func TestDecodeConfigEntry(t *testing.T) {
},
Mode: ProxyModeTransparent,
TransparentProxy: &TransparentProxyConfig{OutboundListenerPort: 808},
Connect: &ConnectConfiguration{
UpstreamConfigs: map[string]UpstreamConfig{
"redis": {
UpstreamConfig: &UpstreamConfiguration{
Overrides: []*UpstreamConfig{
{
Name: "redis",
PassiveHealthCheck: &PassiveHealthCheck{
MaxFailures: 3,
Interval: 2 * time.Second,
},
},
"finance/billing": {
{
Name: "finance--billing",
MeshGateway: MeshGatewayConfig{Mode: "remote"},
},
},
UpstreamDefaults: &UpstreamConfig{
Defaults: &UpstreamConfig{
EnvoyClusterJSON: "zip",
EnvoyListenerJSON: "zop",
Protocol: "http",
ConnectTimeoutMs: 5000,
Limits: &UpstreamLimits{
MaxConnections: 3,
MaxPendingRequests: 4,
MaxConcurrentRequests: 5,
MaxConnections: intPointer(3),
MaxPendingRequests: intPointer(4),
MaxConcurrentRequests: intPointer(5),
},
PassiveHealthCheck: &PassiveHealthCheck{
MaxFailures: 5,
@ -1188,3 +1192,7 @@ func TestDecodeConfigEntry(t *testing.T) {
})
}
}
func intPointer(v int) *int {
return &v
}

View File

@ -74,6 +74,7 @@ function main {
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types"
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/protobuf/empty.proto=github.com/gogo/protobuf/types"
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types"
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/protobuf/wrappers.proto=github.com/gogo/protobuf/types"
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/api/annotations.proto=github.com/gogo/googleapis/google/api"
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/protobuf/field_mask.proto=github.com/gogo/protobuf/types"
gogo_proto_imp_replace="${gogo_proto_imp_replace},Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types"

View File

@ -465,21 +465,32 @@ func TestParseConfigEntry(t *testing.T) {
transparent_proxy = {
outbound_listener_port = 10101
}
connect {
upstream_configs {
"redis" {
upstream_config {
overrides = [
{
name = "redis"
passive_health_check {
max_failures = 3
interval = "2s"
}
}
"finance/billing" {
envoy_listener_json = "{ \"listener-foo\": 5 }"
envoy_cluster_json = "{ \"cluster-bar\": 5 }"
protocol = "grpc"
connect_timeout_ms = 6543
},
{
name = "finance--billing"
mesh_gateway {
mode = "remote"
}
limits {
max_connections = 1111
max_pending_requests = 2222
max_concurrent_requests = 3333
}
}
}
upstream_defaults {
]
defaults {
envoy_cluster_json = "zip"
envoy_listener_json = "zop"
connect_timeout_ms = 5000
@ -512,33 +523,44 @@ func TestParseConfigEntry(t *testing.T) {
TransparentProxy = {
outbound_listener_port = 10101
}
connect = {
upstream_configs = {
"redis" = {
passive_health_check = {
max_failures = 3
interval = "2s"
UpstreamConfig {
Overrides = [
{
Name = "redis"
PassiveHealthCheck {
MaxFailures = 3
Interval = "2s"
}
EnvoyListenerJson = "{ \"listener-foo\": 5 }"
EnvoyClusterJson = "{ \"cluster-bar\": 5 }"
Protocol = "grpc"
ConnectTimeoutMs = 6543
},
{
Name = "finance--billing"
MeshGateway {
Mode = "remote"
}
Limits {
MaxConnections = 1111
MaxPendingRequests = 2222
MaxConcurrentRequests = 3333
}
}
"finance/billing" = {
mesh_gateway = {
mode = "remote"
}
]
Defaults {
EnvoyClusterJson = "zip"
EnvoyListenerJson = "zop"
ConnectTimeoutMs = 5000
Protocol = "http"
Limits {
MaxConnections = 3
MaxPendingRequests = 4
MaxConcurrentRequests = 5
}
}
upstream_defaults = {
envoy_cluster_json = "zip"
envoy_listener_json = "zop"
connect_timeout_ms = 5000
protocol = "http"
limits = {
max_connections = 3
max_pending_requests = 4
max_concurrent_requests = 5
}
passive_health_check = {
max_failures = 5
interval = "4s"
PassiveHealthCheck {
MaxFailures = 5
Interval = "4s"
}
}
}
@ -560,21 +582,32 @@ func TestParseConfigEntry(t *testing.T) {
"transparent_proxy": {
"outbound_listener_port": 10101
},
"connect": {
"upstream_configs": {
"redis": {
"upstream_config": {
"overrides": [
{
"name": "redis",
"passive_health_check": {
"max_failures": 3,
"interval": "2s"
}
},
"envoy_listener_json": "{ \"listener-foo\": 5 }",
"envoy_cluster_json": "{ \"cluster-bar\": 5 }",
"protocol": "grpc",
"connect_timeout_ms": 6543
},
"finance/billing": {
{
"name": "finance--billing",
"mesh_gateway": {
"mode": "remote"
},
"limits": {
"max_connections": 1111,
"max_pending_requests": 2222,
"max_concurrent_requests": 3333
}
}
},
"upstream_defaults": {
],
"defaults": {
"envoy_cluster_json": "zip",
"envoy_listener_json": "zop",
"connect_timeout_ms": 5000,
@ -609,23 +642,34 @@ func TestParseConfigEntry(t *testing.T) {
"TransparentProxy": {
"OutboundListenerPort": 10101
},
"Connect": {
"UpstreamConfigs": {
"redis": {
"UpstreamConfig": {
"Overrides": [
{
"Name": "redis",
"PassiveHealthCheck": {
"MaxFailures": 3,
"Interval": "2s"
}
},
"EnvoyListenerJson": "{ \"listener-foo\": 5 }",
"EnvoyClusterJson": "{ \"cluster-bar\": 5 }",
"Protocol": "grpc",
"ConnectTimeoutMs": 6543
},
"finance/billing": {
{
"Name": "finance--billing",
"MeshGateway": {
"Mode": "remote"
},
"Limits": {
"MaxConnections": 1111,
"MaxPendingRequests": 2222,
"MaxConcurrentRequests": 3333
}
}
},
"UpstreamDefaults": {
"EnvoyClusterJSON": "zip",
"EnvoyListenerJSON": "zop",
],
"Defaults": {
"EnvoyClusterJson": "zip",
"EnvoyListenerJson": "zop",
"ConnectTimeoutMs": 5000,
"Protocol": "http",
"Limits": {
@ -634,8 +678,8 @@ func TestParseConfigEntry(t *testing.T) {
"MaxConcurrentRequests": 5
},
"PassiveHealthCheck": {
"MaxFailures": 5,
"Interval": "4s"
"MaxFailures": 5,
"Interval": "4s"
}
}
}
@ -657,29 +701,40 @@ func TestParseConfigEntry(t *testing.T) {
TransparentProxy: &api.TransparentProxyConfig{
OutboundListenerPort: 10101,
},
Connect: &api.ConnectConfiguration{
UpstreamConfigs: map[string]api.UpstreamConfig{
"redis": {
UpstreamConfig: &api.UpstreamConfiguration{
Overrides: []*api.UpstreamConfig{
{
Name: "redis",
PassiveHealthCheck: &api.PassiveHealthCheck{
MaxFailures: 3,
Interval: 2 * time.Second,
},
EnvoyListenerJSON: `{ "listener-foo": 5 }`,
EnvoyClusterJSON: `{ "cluster-bar": 5 }`,
Protocol: "grpc",
ConnectTimeoutMs: 6543,
},
"finance/billing": {
{
Name: "finance--billing",
MeshGateway: api.MeshGatewayConfig{
Mode: "remote",
},
Limits: &api.UpstreamLimits{
MaxConnections: intPointer(1111),
MaxPendingRequests: intPointer(2222),
MaxConcurrentRequests: intPointer(3333),
},
},
},
UpstreamDefaults: &api.UpstreamConfig{
Defaults: &api.UpstreamConfig{
EnvoyClusterJSON: "zip",
EnvoyListenerJSON: "zop",
Protocol: "http",
ConnectTimeoutMs: 5000,
Limits: &api.UpstreamLimits{
MaxConnections: 3,
MaxPendingRequests: 4,
MaxConcurrentRequests: 5,
MaxConnections: intPointer(3),
MaxPendingRequests: intPointer(4),
MaxConcurrentRequests: intPointer(5),
},
PassiveHealthCheck: &api.PassiveHealthCheck{
MaxFailures: 5,
@ -2677,3 +2732,7 @@ func requireContainsLower(t *testing.T, haystack, needle string) {
t.Helper()
require.Contains(t, strings.ToLower(haystack), strings.ToLower(needle))
}
func intPointer(v int) *int {
return &v
}

View File

@ -12,6 +12,7 @@ func CheckTypeToStructs(s CheckType) structs.CheckType {
t.Notes = s.Notes
t.ScriptArgs = s.ScriptArgs
t.HTTP = s.HTTP
t.H2PING = s.H2PING
t.Header = MapHeadersToStructs(s.Header)
t.Method = s.Method
t.Body = s.Body
@ -21,7 +22,6 @@ func CheckTypeToStructs(s CheckType) structs.CheckType {
t.AliasService = s.AliasService
t.DockerContainerID = s.DockerContainerID
t.Shell = s.Shell
t.H2PING = s.H2PING
t.GRPC = s.GRPC
t.GRPCUseTLS = s.GRPCUseTLS
t.TLSServerName = s.TLSServerName
@ -44,6 +44,7 @@ func NewCheckTypeFromStructs(t structs.CheckType) CheckType {
s.Notes = t.Notes
s.ScriptArgs = t.ScriptArgs
s.HTTP = t.HTTP
s.H2PING = t.H2PING
s.Header = NewMapHeadersFromStructs(t.Header)
s.Method = t.Method
s.Body = t.Body
@ -53,7 +54,6 @@ func NewCheckTypeFromStructs(t structs.CheckType) CheckType {
s.AliasService = t.AliasService
s.DockerContainerID = t.DockerContainerID
s.Shell = t.Shell
s.H2PING = t.H2PING
s.GRPC = t.GRPC
s.GRPCUseTLS = t.GRPCUseTLS
s.TLSServerName = t.TLSServerName
@ -111,6 +111,7 @@ func HealthCheckDefinitionToStructs(s HealthCheckDefinition) structs.HealthCheck
t.Method = s.Method
t.Body = s.Body
t.TCP = s.TCP
t.H2PING = s.H2PING
t.Interval = s.Interval
t.OutputMaxSize = uint(s.OutputMaxSize)
t.Timeout = s.Timeout
@ -118,7 +119,6 @@ func HealthCheckDefinitionToStructs(s HealthCheckDefinition) structs.HealthCheck
t.ScriptArgs = s.ScriptArgs
t.DockerContainerID = s.DockerContainerID
t.Shell = s.Shell
t.H2PING = s.H2PING
t.GRPC = s.GRPC
t.GRPCUseTLS = s.GRPCUseTLS
t.AliasNode = s.AliasNode
@ -135,6 +135,7 @@ func NewHealthCheckDefinitionFromStructs(t structs.HealthCheckDefinition) Health
s.Method = t.Method
s.Body = t.Body
s.TCP = t.TCP
s.H2PING = t.H2PING
s.Interval = t.Interval
s.OutputMaxSize = uint32(t.OutputMaxSize)
s.Timeout = t.Timeout
@ -142,7 +143,6 @@ func NewHealthCheckDefinitionFromStructs(t structs.HealthCheckDefinition) Health
s.ScriptArgs = t.ScriptArgs
s.DockerContainerID = t.DockerContainerID
s.Shell = t.Shell
s.H2PING = t.H2PING
s.GRPC = t.GRPC
s.GRPCUseTLS = t.GRPCUseTLS
s.AliasNode = t.AliasNode

View File

@ -10,12 +10,12 @@ func ConnectProxyConfigToStructs(s ConnectProxyConfig) structs.ConnectProxyConfi
t.DestinationServiceID = s.DestinationServiceID
t.LocalServiceAddress = s.LocalServiceAddress
t.LocalServicePort = int(s.LocalServicePort)
t.Mode = s.Mode
t.Config = ProtobufTypesStructToMapStringInterface(s.Config)
t.Upstreams = UpstreamsToStructs(s.Upstreams)
t.MeshGateway = MeshGatewayConfigToStructs(s.MeshGateway)
t.Expose = ExposeConfigToStructs(s.Expose)
t.TransparentProxy = TransparentProxyConfigToStructs(s.TransparentProxy)
t.Mode = s.Mode
return t
}
func NewConnectProxyConfigFromStructs(t structs.ConnectProxyConfig) ConnectProxyConfig {
@ -24,12 +24,12 @@ func NewConnectProxyConfigFromStructs(t structs.ConnectProxyConfig) ConnectProxy
s.DestinationServiceID = t.DestinationServiceID
s.LocalServiceAddress = t.LocalServiceAddress
s.LocalServicePort = int32(t.LocalServicePort)
s.Mode = t.Mode
s.Config = MapStringInterfaceToProtobufTypesStruct(t.Config)
s.Upstreams = NewUpstreamsFromStructs(t.Upstreams)
s.MeshGateway = NewMeshGatewayConfigFromStructs(t.MeshGateway)
s.Expose = NewExposeConfigFromStructs(t.Expose)
s.TransparentProxy = NewTransparentProxyConfigFromStructs(t.TransparentProxy)
s.Mode = t.Mode
return s
}
func ExposeConfigToStructs(s ExposeConfig) structs.ExposeConfig {
@ -72,16 +72,6 @@ func NewMeshGatewayConfigFromStructs(t structs.MeshGatewayConfig) MeshGatewayCon
s.Mode = t.Mode
return s
}
func TransparentProxyConfigToStructs(s TransparentProxyConfig) structs.TransparentProxyConfig {
var t structs.TransparentProxyConfig
t.OutboundListenerPort = int(s.OutboundListenerPort)
return t
}
func NewTransparentProxyConfigFromStructs(t structs.TransparentProxyConfig) TransparentProxyConfig {
var s TransparentProxyConfig
s.OutboundListenerPort = int32(t.OutboundListenerPort)
return s
}
func ServiceConnectToStructs(s ServiceConnect) structs.ServiceConnect {
var t structs.ServiceConnect
t.Native = s.Native
@ -134,6 +124,16 @@ func NewServiceDefinitionFromStructs(t structs.ServiceDefinition) ServiceDefinit
s.Connect = NewServiceConnectPtrFromStructs(t.Connect)
return s
}
func TransparentProxyConfigToStructs(s TransparentProxyConfig) structs.TransparentProxyConfig {
var t structs.TransparentProxyConfig
t.OutboundListenerPort = int(s.OutboundListenerPort)
return t
}
func NewTransparentProxyConfigFromStructs(t structs.TransparentProxyConfig) TransparentProxyConfig {
var s TransparentProxyConfig
s.OutboundListenerPort = int32(t.OutboundListenerPort)
return s
}
func UpstreamToStructs(s Upstream) structs.Upstream {
var t structs.Upstream
t.DestinationType = s.DestinationType

View File

@ -395,6 +395,7 @@ var xxx_messageInfo_MeshGatewayConfig proto.InternalMessageInfo
// output=service.gen.go
// name=Structs
type TransparentProxyConfig struct {
// mog: func-to=int func-from=int32
OutboundListenerPort int32 `protobuf:"varint,1,opt,name=OutboundListenerPort,proto3" json:"OutboundListenerPort,omitempty"`
}

View File

@ -209,6 +209,7 @@ message MeshGatewayConfig {
// output=service.gen.go
// name=Structs
message TransparentProxyConfig {
// mog: func-to=int func-from=int32
int32 OutboundListenerPort = 1;
}
@ -273,4 +274,4 @@ message Weights {
int32 Passing = 1;
// mog: func-to=int func-from=int32
int32 Warning = 2;
}
}