Merge pull request #4799 from yousong/bugfix/yousong-lb

lb: support redirect type listener & rule
This commit is contained in:
yunion-ci-robot
2020-01-14 02:03:01 +08:00
committed by GitHub
11 changed files with 591 additions and 38 deletions
+32
View File
@@ -275,6 +275,38 @@ var LB_HEALTH_CHECK_HTTP_CODES = choices.NewChoices(
LB_HEALTH_CHECK_HTTP_CODE_5xx,
)
const (
LB_REDIRECT_OFF = "off"
LB_REDIRECT_RAW = "raw"
)
var LB_REDIRECT_TYPES = choices.NewChoices(
LB_REDIRECT_OFF,
LB_REDIRECT_RAW,
)
const (
LB_REDIRECT_CODE_301 = int64(301) // Moved Permanently
LB_REDIRECT_CODE_302 = int64(302) // Found
LB_REDIRECT_CODE_307 = int64(307) // Temporary Redirect
)
var LB_REDIRECT_CODES = []int64{
LB_REDIRECT_CODE_301,
LB_REDIRECT_CODE_302,
LB_REDIRECT_CODE_307,
}
const (
LB_REDIRECT_SCHEME_HTTP = "http"
LB_REDIRECT_SCHEME_HTTPS = "https"
)
var LB_REDIRECT_SCHEMES = choices.NewChoices(
LB_REDIRECT_SCHEME_HTTP,
LB_REDIRECT_SCHEME_HTTPS,
)
const (
LB_BOOL_ON = "on"
LB_BOOL_OFF = "off"
+12
View File
@@ -98,6 +98,18 @@ func newInvalidChoiceError(key string, choices choices.Choices, choice string) e
return newError(ERR_INVALID_CHOICE, "invalid %q, want %s, got %s", key, choices, choice)
}
func newInvalidIntChoiceError(key string, choices []int64, choice int64) error {
wantS := ""
for i, c := range choices {
if i > 0 {
wantS += ", "
}
wantS += fmt.Sprintf("%d", c)
}
gotS := fmt.Sprintf("%d", choice)
return newError(ERR_INVALID_CHOICE, "invalid %q, want %s, got %s", key, wantS, gotS)
}
func newStringTooShortError(key string, got, want int) error {
return newError(ERR_INVALID_LENGTH, "%q too short, got %d, min %s", key, got, want)
}
+133 -9
View File
@@ -26,6 +26,7 @@ import (
"net"
"reflect"
"regexp"
"strconv"
"strings"
"yunion.io/x/jsonutils"
@@ -158,11 +159,64 @@ func (v *ValidatorIPv4Prefix) Validate(data *jsonutils.JSONDict) error {
return nil
}
type ValidatorIntChoices struct {
Validator
choices []int64
Value int64
}
func NewIntChoicesValidator(key string, choices []int64) *ValidatorIntChoices {
v := &ValidatorIntChoices{
Validator: Validator{Key: key},
choices: choices,
}
v.SetParent(v)
return v
}
func (v *ValidatorIntChoices) has(i int64) bool {
for _, c := range v.choices {
if c == i {
return true
}
}
return false
}
func (v *ValidatorIntChoices) Default(i int64) IValidator {
if v.has(i) {
v.Validator.Default(i)
return v
}
panic("invalid default for " + v.Key)
}
func (v *ValidatorIntChoices) getValue() interface{} {
return v.Value
}
func (v *ValidatorIntChoices) Validate(data *jsonutils.JSONDict) error {
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
return err
}
i, err := v.value.Int()
if err != nil {
return newGeneralError(v.Key, err)
}
if !v.has(i) {
return newInvalidIntChoiceError(v.Key, v.choices, i)
}
// in case it's stringified from v.value
data.Set(v.Key, jsonutils.NewInt(i))
v.Value = i
return nil
}
type ValidatorStringChoices struct {
Validator
Choices choices.Choices
defaultVal string
Value string
Choices choices.Choices
Value string
}
func NewStringChoicesValidator(key string, choices choices.Choices) *ValidatorStringChoices {
@@ -176,7 +230,8 @@ func NewStringChoicesValidator(key string, choices choices.Choices) *ValidatorSt
func (v *ValidatorStringChoices) Default(s string) IValidator {
if v.Choices.Has(s) {
return v.Validator.Default(s)
v.Validator.Default(s)
return v
}
panic("invalid default for " + v.Key)
}
@@ -204,11 +259,10 @@ func (v *ValidatorStringChoices) Validate(data *jsonutils.JSONDict) error {
type ValidatorStringMultiChoices struct {
Validator
Choices choices.Choices
defaultVal string
Value string
sep string
keepDup bool
Choices choices.Choices
Value string
sep string
keepDup bool
}
func NewStringMultiChoicesValidator(key string, choices choices.Choices) *ValidatorStringMultiChoices {
@@ -589,6 +643,76 @@ func NewDomainNameValidator(key string) *ValidatorDomainName {
return v
}
type ValidatorHostPort struct {
ValidatorRegexp
optionalPort bool
Domain string
Port int
Value string
}
var regHostPort *regexp.Regexp
func init() {
// guard against surprise
exp := regutils.DOMAINNAME_REG.String()
if exp != "" && exp[len(exp)-1] == '$' {
exp = exp[:len(exp)-1]
}
exp += "(?::[0-9]{1,5})?"
regHostPort = regexp.MustCompile(exp)
}
func NewHostPortValidator(key string) *ValidatorHostPort {
v := &ValidatorHostPort{
ValidatorRegexp: *NewRegexpValidator(key, regHostPort),
}
v.SetParent(v)
return v
}
func (v *ValidatorHostPort) getValue() interface{} {
return v.Value
}
func (v *ValidatorHostPort) OptionalPort(optionalPort bool) *ValidatorHostPort {
v.optionalPort = optionalPort
return v
}
func (v *ValidatorHostPort) Validate(data *jsonutils.JSONDict) error {
err := v.ValidatorRegexp.Validate(data)
if err != nil {
return err
}
hostPort := v.ValidatorRegexp.Value
if hostPort == "" && (v.optional || v.allowEmpty) {
return nil
}
i := strings.IndexRune(hostPort, ':')
if i < 0 {
if v.optionalPort {
v.Value = hostPort
v.Domain = hostPort
return nil
}
return newInvalidValueError(v.Key, "port missing")
}
portStr := hostPort[i+1:]
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return newInvalidValueError(v.Key, "bad port integer: "+err.Error())
}
if port <= 0 {
return newInvalidValueError(v.Key, "negative port")
}
v.Value = hostPort
v.Domain = hostPort[:i]
v.Port = int(port)
return nil
}
type ValidatorURLPath struct {
ValidatorRegexp
}
@@ -52,6 +52,18 @@ func TestURLPathRegexp(t *testing.T) {
}
}
func TestRegHostPort(t *testing.T) {
inputs := []string{
"www.yunion.cn",
"www.yunion.cn:9000",
}
for _, in := range inputs {
if !regHostPort.Match([]byte(in)) {
t.Errorf("should match: %q", in)
}
}
}
type C struct {
Name string
In string
@@ -161,6 +173,73 @@ func TestStringChoicesValidator(t *testing.T) {
}
}
func TestIntChoicesValidator(t *testing.T) {
choices := []int64{-1, 0, 100}
cases := []*C{
{
Name: "missing non-optional",
In: `{}`,
Out: `{}`,
Optional: false,
Err: ERR_MISSING_KEY,
ValueWant: int64(0),
},
{
Name: "missing optional",
In: `{}`,
Out: `{}`,
Optional: true,
ValueWant: int64(0),
},
{
Name: "missing with default",
In: `{}`,
Out: `{s: -1}`,
Default: int64(-1),
ValueWant: int64(-1),
},
{
Name: "stringified",
In: `{"s": "100"}`,
Out: `{s: 100}`,
ValueWant: int64(100),
},
{
Name: "stringified invalid choice",
In: `{"s": "101"}`,
Out: `{"s": "101"}`,
Err: ERR_INVALID_CHOICE,
ValueWant: int64(0),
},
{
Name: "good choice",
In: `{"s": 0}`,
Out: `{"s": 0}`,
ValueWant: int64(0),
},
{
Name: "bad choice",
In: `{"s": 101}`,
Out: `{"s": 101}`,
Err: ERR_INVALID_CHOICE,
ValueWant: int64(0),
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
v := NewIntChoicesValidator("s", choices)
if c.Default != nil {
s := c.Default.(int64)
v.Default(s)
}
if c.Optional {
v.Optional(true)
}
testS(t, v, c)
})
}
}
func TestStringMultiChoicesValidator(t *testing.T) {
type MultiChoicesC struct {
*C
@@ -487,6 +566,134 @@ func TestRegexValidator(t *testing.T) {
}
}
func TestHostPortValidator(t *testing.T) {
type HostPortC struct {
*C
AllowEmpty bool
OptionalPort bool
}
cases := []*HostPortC{
{
C: &C{
Name: "missing non-optional",
In: `{}`,
Out: `{}`,
Err: ERR_MISSING_KEY,
ValueWant: "",
},
},
{
C: &C{
Name: "missing optional",
In: `{}`,
Out: `{}`,
Optional: true,
ValueWant: "",
},
},
{
C: &C{
Name: "missing with default",
In: `{}`,
Out: `{s: "example.com"}`,
Default: "example.com",
ValueWant: "example.com",
},
OptionalPort: true,
},
{
C: &C{
Name: "missing with default (has port)",
In: `{}`,
Out: `{s: "example.com:9000"}`,
Default: "example.com:9000",
ValueWant: "example.com:9000",
},
},
{
C: &C{
Name: "valid",
In: `{s: "a.example.com"}`,
Out: `{s: "a.example.com"}`,
ValueWant: "a.example.com",
},
OptionalPort: true,
},
{
C: &C{
Name: "valid (has port)",
In: `{s: "a.example.com:9000"}`,
Out: `{s: "a.example.com:9000"}`,
ValueWant: "a.example.com:9000",
},
},
{
C: &C{
Name: "valid (allow empty)",
In: `{s: ""}`,
Out: `{s: ""}`,
ValueWant: "",
},
AllowEmpty: true,
},
{
C: &C{
Name: "invalid (domain)",
In: `{s: "/.example.com:9000"}`,
Out: `{s: "/.example.com:9000"}`,
ValueWant: "",
Err: ERR_INVALID_VALUE,
},
},
{
C: &C{
Name: "invalid (port)",
In: `{s: "/.example.com:65536"}`,
Out: `{s: "/.example.com:65536"}`,
ValueWant: "",
Err: ERR_INVALID_VALUE,
},
},
{
C: &C{
Name: "invalid (no port)",
In: `{s: "a.example.com"}`,
Out: `{s: "a.example.com"}`,
ValueWant: "",
Err: ERR_INVALID_VALUE,
},
},
{
C: &C{
Name: "invalid (disallow empty)",
In: `{s: ""}`,
Out: `{s: ""}`,
ValueWant: "",
Err: ERR_INVALID_VALUE,
},
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
v := NewHostPortValidator("s")
if c.Default != nil {
i := c.Default.(string)
v.Default(i)
}
if c.Optional {
v.Optional(true)
}
if c.OptionalPort {
v.OptionalPort(true)
}
if c.AllowEmpty {
v.AllowEmpty(true)
}
testS(t, v, c.C)
})
}
}
func TestIPv4Validator(t *testing.T) {
var nilIP net.IP
localIP := net.IPv4(127, 0, 0, 1).To4()
+12 -10
View File
@@ -72,6 +72,7 @@ type SLoadbalancerListenerRule struct {
SLoadbalancerHealthCheck // 目前只有腾讯云HTTP、HTTPS类型的健康检查是和规则绑定的。
SLoadbalancerHTTPRateLimiter
SLoadbalancerHTTPRedirect
}
func ValidateListenerRuleConditions(condition string) error {
@@ -409,16 +410,8 @@ func (man *SLoadbalancerListenerRuleManager) ValidateCreateData(ctx context.Cont
data.Update(jsonutils.Marshal(input))
listenerV := validators.NewModelIdOrNameValidator("listener", "loadbalancerlistener", ownerId)
backendGroupV := validators.NewModelIdOrNameValidator("backend_group", "loadbalancerbackendgroup", ownerId)
keyV := map[string]validators.IValidator{
"listener": listenerV,
"backend_group": backendGroupV,
}
for _, v := range keyV {
if err := v.Validate(data); err != nil {
return nil, err
}
if err := listenerV.Validate(data); err != nil {
return nil, err
}
listener := listenerV.Model.(*SLoadbalancerListener)
@@ -427,6 +420,15 @@ func (man *SLoadbalancerListenerRuleManager) ValidateCreateData(ctx context.Cont
return nil, httperrors.NewResourceNotFoundError("failed to find region for loadbalancer listener %s", listener.Name)
}
backendGroupV := validators.NewModelIdOrNameValidator("backend_group", "loadbalancerbackendgroup", ownerId)
if region.Provider == api.CLOUD_PROVIDER_ONECLOUD {
// backend group can be empty if you support redirect in rule
backendGroupV.Optional(true)
}
if err := backendGroupV.Validate(data); err != nil {
return nil, err
}
return region.GetDriver().ValidateCreateLoadbalancerListenerRuleData(ctx, userCred, ownerId, data, backendGroupV.Model)
}
@@ -96,6 +96,14 @@ type SLoadbalancerHTTPListener struct {
Gzip bool `nullable:"true" list:"user" create:"optional" update:"user"` // Gzip数据压缩
}
type SLoadbalancerHTTPRedirect struct {
Redirect string `nullable:"true" list:"user" create:"optional" update:"user" default:"off"` // 跳转类型
RedirectCode int `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转HTTP code
RedirectScheme string `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转uri scheme
RedirectHost string `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转时变更Host
RedirectPath string `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转时变更Path
}
// TODO
//
// - CACertificate string
@@ -145,6 +153,7 @@ type SLoadbalancerListener struct {
SLoadbalancerHealthCheck
SLoadbalancerHTTPRateLimiter
SLoadbalancerHTTPRedirect
}
func (man *SLoadbalancerListenerManager) CheckListenerUniqueness(ctx context.Context, lb *SLoadbalancer, listenerType string, listenerPort int64) error {
+67 -4
View File
@@ -281,8 +281,9 @@ func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerBackendData(ctx context.
func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) {
listenerV := validators.NewModelIdOrNameValidator("listener", "loadbalancerlistener", ownerId)
domainV := validators.NewDomainNameValidator("domain")
domainV := validators.NewHostPortValidator("domain").OptionalPort(true)
pathV := validators.NewURLPathValidator("path")
redirectV := validators.NewStringChoicesValidator("redirect", api.LB_REDIRECT_TYPES)
keyV := map[string]validators.IValidator{
"status": validators.NewStringChoicesValidator("status", api.LB_STATUS_SPEC).Default(api.LB_STATUS_ENABLED),
@@ -292,6 +293,12 @@ func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx con
"http_request_rate": validators.NewNonNegativeValidator("http_request_rate").Default(0),
"http_request_rate_per_src": validators.NewNonNegativeValidator("http_request_rate_per_src").Default(0),
"redirect": redirectV.Default(api.LB_REDIRECT_OFF),
"redirect_code": validators.NewIntChoicesValidator("redirect_code", api.LB_REDIRECT_CODES).Default(api.LB_REDIRECT_CODE_302),
"redirect_scheme": validators.NewStringChoicesValidator("redirect_scheme", api.LB_REDIRECT_SCHEMES).Optional(true),
"redirect_host": validators.NewHostPortValidator("redirect_host").OptionalPort(true).Optional(true),
"redirect_path": validators.NewURLPathValidator("redirect_path").Optional(true),
}
if err := RunValidators(keyV, data, false); err != nil {
@@ -303,10 +310,20 @@ func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx con
if listenerType != api.LB_LISTENER_TYPE_HTTP && listenerType != api.LB_LISTENER_TYPE_HTTPS {
return nil, httperrors.NewInputParameterError("listener type must be http/https, got %s", listenerType)
}
if listener.Redirect != api.LB_REDIRECT_OFF {
return nil, httperrors.NewInputParameterError("do not allow adding rules for redirect listener")
}
if lbbg, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != listener.LoadbalancerId {
return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s",
lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, listener.LoadbalancerId)
{
if redirectV.Value == api.LB_REDIRECT_OFF {
if backendGroup == nil {
return nil, httperrors.NewInputParameterError("backend_group argument is missing")
}
}
if lbbg, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != listener.LoadbalancerId {
return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s",
lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, listener.LoadbalancerId)
}
}
err := models.LoadbalancerListenerRuleCheckUniqueness(ctx, listener, domainV.Value, pathV.Value)
@@ -321,9 +338,22 @@ func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx con
func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) {
lbr := ctx.Value("lbr").(*models.SLoadbalancerListenerRule)
domainV := validators.NewHostPortValidator("domain").OptionalPort(true)
pathV := validators.NewURLPathValidator("path")
redirectV := validators.NewStringChoicesValidator("redirect", api.LB_REDIRECT_TYPES)
redirectV.Default(lbr.Redirect)
keyV := map[string]validators.IValidator{
"domain": domainV.AllowEmpty(true).Default(lbr.Domain),
"path": pathV.Default(lbr.Path),
"http_request_rate": validators.NewNonNegativeValidator("http_request_rate"),
"http_request_rate_per_src": validators.NewNonNegativeValidator("http_request_rate_per_src"),
"redirect": redirectV,
"redirect_code": validators.NewIntChoicesValidator("redirect_code", api.LB_REDIRECT_CODES),
"redirect_scheme": validators.NewStringChoicesValidator("redirect_scheme", api.LB_REDIRECT_SCHEMES),
"redirect_host": validators.NewHostPortValidator("redirect_host").OptionalPort(true),
"redirect_path": validators.NewURLPathValidator("redirect_path"),
}
for _, v := range keyV {
v.Optional(true)
@@ -331,6 +361,11 @@ func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerListenerRuleData(ctx con
return nil, err
}
}
if lbr.Redirect != redirectV.Value {
// this can be relaxed to do not allow on/off
return nil, httperrors.NewInputParameterError("do not allow changing redirect type")
}
if backendGroup, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && backendGroup.Id != lbr.BackendGroupId {
listenerM, err := models.LoadbalancerListenerManager.FetchById(lbr.ListenerId)
if err != nil {
@@ -352,6 +387,7 @@ func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context
aclStatusV := validators.NewStringChoicesValidator("acl_status", api.LB_BOOL_VALUES)
aclTypeV := validators.NewStringChoicesValidator("acl_type", api.LB_ACL_TYPES)
aclV := validators.NewModelIdOrNameValidator("acl", "loadbalanceracl", ownerId)
redirectV := validators.NewStringChoicesValidator("redirect", api.LB_REDIRECT_TYPES)
keyV := map[string]validators.IValidator{
"status": validators.NewStringChoicesValidator("status", api.LB_STATUS_SPEC).Default(api.LB_STATUS_ENABLED),
@@ -382,6 +418,12 @@ func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context
"http_request_rate": validators.NewNonNegativeValidator("http_request_rate").Default(0),
"http_request_rate_per_src": validators.NewNonNegativeValidator("http_request_rate_per_src").Default(0),
"redirect": redirectV.Default(api.LB_REDIRECT_OFF),
"redirect_code": validators.NewIntChoicesValidator("redirect_code", api.LB_REDIRECT_CODES).Default(api.LB_REDIRECT_CODE_302),
"redirect_scheme": validators.NewStringChoicesValidator("redirect_scheme", api.LB_REDIRECT_SCHEMES).Optional(true),
"redirect_host": validators.NewHostPortValidator("redirect_host").OptionalPort(true).Optional(true),
"redirect_path": validators.NewURLPathValidator("redirect_path").Optional(true),
}
if err := RunValidators(keyV, data, false); err != nil {
@@ -395,6 +437,13 @@ func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context
return nil, err
}
redirectType := redirectV.Value
if redirectType != api.LB_REDIRECT_OFF {
if listenerType != api.LB_LISTENER_TYPE_HTTP && listenerType != api.LB_LISTENER_TYPE_HTTPS {
return nil, httperrors.NewInputParameterError("redirect can only be enabled for http/https listener")
}
}
// backendgroup check
if lbbg, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != lb.Id {
return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s",
@@ -460,6 +509,9 @@ func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx context
aclV.Default(lblis.AclId)
}
redirectV := validators.NewStringChoicesValidator("redirect", api.LB_REDIRECT_TYPES)
redirectV.Default(lblis.Redirect)
certV := validators.NewModelIdOrNameValidator("certificate", "loadbalancercertificate", ownerId)
tlsCipherPolicyV := validators.NewStringChoicesValidator("tls_cipher_policy", api.LB_TLS_CIPHER_POLICIES).Default(api.LB_TLS_CIPHER_POLICY_1_2)
keyV := map[string]validators.IValidator{
@@ -503,12 +555,23 @@ func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx context
"certificate": certV,
"tls_cipher_policy": tlsCipherPolicyV,
"enable_http2": validators.NewBoolValidator("enable_http2"),
"redirect": redirectV,
"redirect_code": validators.NewIntChoicesValidator("redirect_code", api.LB_REDIRECT_CODES),
"redirect_scheme": validators.NewStringChoicesValidator("redirect_scheme", api.LB_REDIRECT_SCHEMES),
"redirect_host": validators.NewHostPortValidator("redirect_host").OptionalPort(true),
"redirect_path": validators.NewURLPathValidator("redirect_path"),
}
if err := RunValidators(keyV, data, true); err != nil {
return nil, err
}
if lblis.Redirect != redirectV.Value {
// this can be relaxed to do not allow on/off
return nil, httperrors.NewInputParameterError("do not allow changing redirect type")
}
if err := models.LoadbalancerListenerManager.ValidateAcl(aclStatusV, aclTypeV, aclV, data, lblis.GetProviderName()); err != nil {
return nil, err
}
+68 -15
View File
@@ -26,7 +26,9 @@ import (
"yunion.io/x/log"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
agentutils "yunion.io/x/onecloud/pkg/lbagent/utils"
"yunion.io/x/onecloud/pkg/mcclient/models"
)
var haproxyConfigErrNop = errors.New("nop haproxy config snippet")
@@ -387,35 +389,35 @@ func (b *LoadbalancerCorpus) genHaproxyConfigHttpRate(data map[string]interface{
return nil
}
func (b *LoadbalancerCorpus) genHaproxyConfigHttp(buf *bytes.Buffer, listener *LoadbalancerListener, opts *AgentParams) error {
func (b *LoadbalancerCorpus) genHaproxyConfigHttpRedirectOff(listener *LoadbalancerListener, data map[string]interface{}) error {
lb := listener.loadbalancer
rules := listener.rules.OrderedEnabledList()
data := b.genHaproxyConfigCommon(lb, listener, opts)
ruleBackendIdGen := func(id string) string {
return fmt.Sprintf("backends_rule-%s", id)
}
{
// NOTE add X-Real-IP if needed
//
// http-request set-header X-Client-IP %[src]
//
data["xforwardedfor"] = listener.XForwardedFor
data["gzip"] = listener.Gzip
}
{ // use_backend rule.Id if xx
ruleLines := []string{}
for _, rule := range rules {
ruleLine := fmt.Sprintf("use_backend %s", ruleBackendIdGen(rule.Id))
sufCond := ""
if rule.Domain != "" || rule.Path != "" {
ruleLine += " if"
sufCond += " if"
if rule.Domain != "" {
ruleLine += fmt.Sprintf(" { hdr_dom(host) %q }", rule.Domain)
sufCond += fmt.Sprintf(" { hdr_dom(host) %q }", rule.Domain)
}
if rule.Path != "" {
ruleLine += fmt.Sprintf(" { path_beg %q }", rule.Path)
sufCond += fmt.Sprintf(" { path_beg %q }", rule.Path)
}
}
ruleLines = append(ruleLines, ruleLine)
if rule.Redirect == computeapi.LB_REDIRECT_OFF {
ruleLine := fmt.Sprintf("use_backend %s", ruleBackendIdGen(rule.Id))
ruleLines = append(ruleLines, ruleLine+sufCond)
continue
} else if rule.Redirect == computeapi.LB_REDIRECT_RAW {
ruleLine := b.haproxyRedirectLine(&rule.LoadbalancerHTTPRedirect, listener.ListenerType)
ruleLines = append(ruleLines, ruleLine+sufCond)
} else {
return haproxyConfigErrNop
}
}
data["rules"] = ruleLines
}
@@ -428,6 +430,9 @@ func (b *LoadbalancerCorpus) genHaproxyConfigHttp(buf *bytes.Buffer, listener *L
// just in case
continue
}
if rule.Redirect != computeapi.LB_REDIRECT_OFF {
continue
}
backendGroup := lb.backendGroups[rule.BackendGroupId]
backendData := map[string]interface{}{
"comment": fmt.Sprintf("rule %s(%s) backendGroup %s(%s)",
@@ -467,6 +472,54 @@ func (b *LoadbalancerCorpus) genHaproxyConfigHttp(buf *bytes.Buffer, listener *L
}
data["backends"] = backends
}
return nil
}
func (b *LoadbalancerCorpus) genHaproxyConfigHttpRedirectRaw(listener *LoadbalancerListener, data map[string]interface{}) error {
data["rules"] = []string{
b.haproxyRedirectLine(&listener.LoadbalancerHTTPRedirect, listener.ListenerType),
}
return nil
}
func (b *LoadbalancerCorpus) haproxyRedirectLine(r *models.LoadbalancerHTTPRedirect, listenerType string) string {
var (
code = r.RedirectCode
scheme = r.RedirectScheme
host = r.RedirectHost
path = r.RedirectPath
)
if scheme == "" {
scheme = strings.ToLower(listenerType)
}
if host == "" {
host = "%[req.hdr(host)]"
}
if path == "" {
path = "%[capture.req.uri]"
}
line := fmt.Sprintf("http-request redirect code %d location %s://%s%s", code, scheme, host, path)
return line
}
func (b *LoadbalancerCorpus) genHaproxyConfigHttp(buf *bytes.Buffer, listener *LoadbalancerListener, opts *AgentParams) error {
lb := listener.loadbalancer
data := b.genHaproxyConfigCommon(lb, listener, opts)
{
// NOTE add X-Real-IP if needed
//
// http-request set-header X-Client-IP %[src]
//
data["xforwardedfor"] = listener.XForwardedFor
data["gzip"] = listener.Gzip
}
if listener.Redirect == computeapi.LB_REDIRECT_OFF {
b.genHaproxyConfigHttpRedirectOff(listener, data)
} else if listener.Redirect == computeapi.LB_REDIRECT_RAW {
b.genHaproxyConfigHttpRedirectRaw(listener, data)
} else {
return haproxyConfigErrNop
}
err := haproxyConfigTmpl.ExecuteTemplate(buf, "httpListen", data)
return err
}
+9
View File
@@ -62,6 +62,13 @@ type LoadbalancerHTTPRateLimiter struct {
HTTPRequestRatePerSrc int
}
type LoadbalancerHTTPRedirect struct {
Redirect string
RedirectCode int
RedirectScheme string
RedirectHost string
RedirectPath string
}
type LoadbalancerListener struct {
VirtualResource
ManagedResource
@@ -109,6 +116,7 @@ type LoadbalancerListener struct {
LoadbalancerHTTPSListener
LoadbalancerHTTPRateLimiter
LoadbalancerHTTPRedirect
}
type LoadbalancerListenerRule struct {
@@ -123,6 +131,7 @@ type LoadbalancerListenerRule struct {
Path string
LoadbalancerHTTPRateLimiter
LoadbalancerHTTPRedirect
}
type LoadbalancerBackendGroup struct {
@@ -20,6 +20,15 @@ type LoadbalancerListenerRuleCreateOptions struct {
BackendGroup string
Domain string
Path string
HTTPRequestRate *int
HTTPRequestRatePerSrc *int
Redirect *string `choices:"off|raw"`
RedirectCode *int `choices:"301|302|307"`
RedirectScheme *string `choices:"http|https"`
RedirectHost *string
RedirectPath *string
}
type LoadbalancerListenerRuleListOptions struct {
@@ -29,6 +38,12 @@ type LoadbalancerListenerRuleListOptions struct {
Listener string
Domain string
Path string
Redirect *string `choices:"off|raw"`
RedirectCode *int `choices:"301|302|307"`
RedirectScheme *string `choices:"http|https"`
RedirectHost *string
RedirectPath *string
}
type LoadbalancerListenerRuleUpdateOptions struct {
@@ -36,6 +51,15 @@ type LoadbalancerListenerRuleUpdateOptions struct {
Name string
BackendGroup string
HTTPRequestRate *int
HTTPRequestRatePerSrc *int
Redirect *string `choices:"off|raw"`
RedirectCode *int `choices:"301|302|307"`
RedirectScheme *string `choices:"http|https"`
RedirectHost *string
RedirectPath *string
}
type LoadbalancerListenerRuleGetOptions struct {
@@ -67,6 +67,12 @@ type LoadbalancerListenerCreateOptions struct {
HTTPRequestRate *int
HTTPRequestRatePerSrc *int
Redirect *string `choices:"off|raw"`
RedirectCode *int `choices:"301|302|307"`
RedirectScheme *string `choices:"http|https"`
RedirectHost *string
RedirectPath *string
}
type LoadbalancerListenerListOptions struct {
@@ -119,6 +125,12 @@ type LoadbalancerListenerListOptions struct {
HTTPRequestRate *int
HTTPRequestRatePerSrc *int
Redirect *string `choices:"off|raw"`
RedirectCode *int `choices:"301|302|307"`
RedirectScheme *string `choices:"http|https"`
RedirectHost *string
RedirectPath *string
}
type LoadbalancerListenerUpdateOptions struct {
@@ -169,6 +181,12 @@ type LoadbalancerListenerUpdateOptions struct {
HTTPRequestRate *int
HTTPRequestRatePerSrc *int
Redirect *string `choices:"off|raw"`
RedirectCode *int `choices:"301|302|307"`
RedirectScheme *string `choices:"http|https"`
RedirectHost *string
RedirectPath *string
}
type LoadbalancerListenerGetOptions struct {