allow kb granularity for lua shared dicts (#6750)

Update internal/ingress/controller/template/configmap.go

Co-authored-by: Ricardo Katz <rikatz@users.noreply.github.com>

Co-authored-by: Ricardo Katz <rikatz@users.noreply.github.com>
This commit is contained in:
Matthew Silverman 2021-08-12 14:13:50 -04:00 committed by GitHub
parent b510b0e930
commit b591adac48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 34 deletions

View file

@ -1046,6 +1046,12 @@ For example following will set default `certificate_data` dictionary to `100M` a
lua-shared-dicts: "certificate_data: 100, my_custom_plugin: 5" lua-shared-dicts: "certificate_data: 100, my_custom_plugin: 5"
``` ```
You can optionally set a size unit to allow for kilobyte-granularity. Allowed units are 'm' or 'k' (case-insensitive), and it defaults to MB if no unit is provided. Here is a similar example, but the `my_custom_plugin` dict is only 512KB.
```
lua-shared-dicts: "certificate_data: 100, my_custom_plugin: 512k"
```
_References:_ _References:_
[http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after](http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after) [http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after](http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after)

View file

@ -229,7 +229,7 @@ type NGINXController struct {
// runningConfig contains the running configuration in the Backend // runningConfig contains the running configuration in the Backend
runningConfig *ingress.Configuration runningConfig *ingress.Configuration
t ngx_template.TemplateWriter t ngx_template.Writer
resolver []net.IP resolver []net.IP

View file

@ -19,6 +19,7 @@ package template
import ( import (
"fmt" "fmt"
"net" "net"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -67,21 +68,22 @@ const (
var ( var (
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...) validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
dictSizeRegex = regexp.MustCompile(`^(\d+)([kKmM])?$`)
defaultLuaSharedDicts = map[string]int{ defaultLuaSharedDicts = map[string]int{
"configuration_data": 20, "configuration_data": 20480,
"certificate_data": 20, "certificate_data": 20480,
"balancer_ewma": 10, "balancer_ewma": 10240,
"balancer_ewma_last_touched_at": 10, "balancer_ewma_last_touched_at": 10240,
"balancer_ewma_locks": 1, "balancer_ewma_locks": 1024,
"certificate_servers": 5, "certificate_servers": 5120,
"ocsp_response_cache": 5, // keep this same as certificate_servers "ocsp_response_cache": 5120, // keep this same as certificate_servers
"global_throttle_cache": 10, "global_throttle_cache": 10240,
} }
defaultGlobalAuthRedirectParam = "rd" defaultGlobalAuthRedirectParam = "rd"
) )
const ( const (
maxAllowedLuaDictSize = 200 maxAllowedLuaDictSize = 204800
maxNumberOfLuaDicts = 100 maxNumberOfLuaDicts = 100
) )
@ -117,18 +119,18 @@ func ReadConfig(src map[string]string) config.Configuration {
v = strings.Replace(v, " ", "", -1) v = strings.Replace(v, " ", "", -1)
results := strings.SplitN(v, ":", 2) results := strings.SplitN(v, ":", 2)
dictName := results[0] dictName := results[0]
size, err := strconv.Atoi(results[1]) size := dictStrToKb(results[1])
if err != nil { if size < 0 {
klog.Errorf("Ignoring non integer value %v for Lua dictionary %v: %v.", results[1], dictName, err) klog.Errorf("Ignoring poorly formatted value %v for Lua dictionary %v", results[1], dictName)
continue continue
} }
if size > maxAllowedLuaDictSize { if size > maxAllowedLuaDictSize {
klog.Errorf("Ignoring %v for Lua dictionary %v: maximum size is %v.", size, dictName, maxAllowedLuaDictSize) klog.Errorf("Ignoring %v for Lua dictionary %v: maximum size is %vk.", results[1], dictName, maxAllowedLuaDictSize)
continue continue
} }
if len(luaSharedDicts)+1 > maxNumberOfLuaDicts { if len(luaSharedDicts)+1 > maxNumberOfLuaDicts {
klog.Errorf("Ignoring %v for Lua dictionary %v: can not configure more than %v dictionaries.", klog.Errorf("Ignoring %v for Lua dictionary %v: can not configure more than %v dictionaries.",
size, dictName, maxNumberOfLuaDicts) results[1], dictName, maxNumberOfLuaDicts)
continue continue
} }
@ -427,3 +429,22 @@ func splitAndTrimSpace(s, sep string) []string {
return values return values
} }
func dictStrToKb(sizeStr string) int {
sizeMatch := dictSizeRegex.FindStringSubmatch(sizeStr)
if sizeMatch == nil {
return -1
}
size, _ := strconv.Atoi(sizeMatch[1]) // validated already with regex
if sizeMatch[2] == "" || strings.ToLower(sizeMatch[2]) == "m" {
size *= 1024
}
return size
}
func dictKbToStr(size int) string {
if size%1024 == 0 {
return fmt.Sprintf("%dM", size/1024)
}
return fmt.Sprintf("%dK", size)
}

View file

@ -344,27 +344,32 @@ func TestLuaSharedDictsParsing(t *testing.T) {
{ {
name: "configuration_data only", name: "configuration_data only",
entry: map[string]string{"lua-shared-dicts": "configuration_data:5"}, entry: map[string]string{"lua-shared-dicts": "configuration_data:5"},
expect: map[string]int{"configuration_data": 5}, expect: map[string]int{"configuration_data": 5120},
}, },
{ {
name: "certificate_data only", name: "certificate_data only",
entry: map[string]string{"lua-shared-dicts": "certificate_data: 4"}, entry: map[string]string{"lua-shared-dicts": "certificate_data: 4"},
expect: map[string]int{"certificate_data": 4}, expect: map[string]int{"certificate_data": 4096},
}, },
{ {
name: "custom dicts", name: "custom dicts",
entry: map[string]string{"lua-shared-dicts": "configuration_data: 10, my_random_dict:15 , another_example:2"}, entry: map[string]string{"lua-shared-dicts": "configuration_data: 10, my_random_dict:15 , another_example:2"},
expect: map[string]int{"configuration_data": 10, "my_random_dict": 15, "another_example": 2}, expect: map[string]int{"configuration_data": 10240, "my_random_dict": 15360, "another_example": 2048},
}, },
{ {
name: "invalid size value should be ignored", name: "invalid size value should be ignored",
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 1a"}, entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 1a, bad_mb_dict:10mb"},
expect: map[string]int{"mydict": 10}, expect: map[string]int{"mydict": 10240},
}, },
{ {
name: "dictionary size can not be larger than 200", name: "dictionary size can not be larger than 200",
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 201"}, entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 201, invalid_kb: 204801k"},
expect: map[string]int{"mydict": 10}, expect: map[string]int{"mydict": 10240},
},
{
name: "specified units are interpreted properly",
entry: map[string]string{"lua-shared-dicts": "kb_dict_a: 512k, mb_dict_a: 30m, kb_dict_b:16K, mb_dict_b:4M"},
expect: map[string]int{"kb_dict_a": 512, "mb_dict_a": 30720, "kb_dict_b": 16, "mb_dict_b": 4096},
}, },
} }
@ -418,3 +423,76 @@ func TestSplitAndTrimSpace(t *testing.T) {
} }
} }
} }
func TestDictStrToKb(t *testing.T) {
testCases := []struct {
name string
input string
expect int
}{
{
name: "unitless int size converted to kb",
input: "50",
expect: 51200,
},
{
name: "lowercase k accepted",
input: "512k",
expect: 512,
},
{
name: "uppercase K accepted",
input: "512K",
expect: 512,
},
{
name: "lowercase m accepted",
input: "10m",
expect: 10240,
},
{
name: "uppercase M accepted",
input: "10M",
expect: 10240,
},
{
name: "trailing characters fail",
input: "50kb",
expect: -1,
},
{
name: "leading characters fail",
input: " 50k",
expect: -1,
},
}
for _, tc := range testCases {
if size := dictStrToKb(tc.input); size != tc.expect {
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", tc.name, tc.expect, size)
}
}
}
func TestDictKbToStr(t *testing.T) {
testCases := []struct {
name string
input int
expect string
}{
{
name: "mod 1024 reports as M",
input: 5120,
expect: "5M",
},
{
name: "non-mod 1024 reports as K",
input: 5001,
expect: "5001K",
},
}
for _, tc := range testCases {
if sizeStr := dictKbToStr(tc.input); sizeStr != tc.expect {
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", tc.name, tc.expect, sizeStr)
}
}
}

View file

@ -60,8 +60,8 @@ const (
stateComment stateComment
) )
// TemplateWriter is the interface to render a template // Writer is the interface to render a template
type TemplateWriter interface { type Writer interface {
Write(conf config.TemplateConfig) ([]byte, error) Write(conf config.TemplateConfig) ([]byte, error)
} }
@ -329,7 +329,8 @@ func buildLuaSharedDictionaries(c interface{}, s interface{}) string {
} }
for name, size := range cfg.LuaSharedDicts { for name, size := range cfg.LuaSharedDicts {
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size)) sizeStr := dictKbToStr(size)
out = append(out, fmt.Sprintf("lua_shared_dict %s %s", name, sizeStr))
} }
sort.Strings(out) sort.Strings(out)
@ -341,16 +342,16 @@ func luaConfigurationRequestBodySize(c interface{}) string {
cfg, ok := c.(config.Configuration) cfg, ok := c.(config.Configuration)
if !ok { if !ok {
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c) klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
return "100" // just a default number return "100M" // just a default number
} }
size := cfg.LuaSharedDicts["configuration_data"] size := cfg.LuaSharedDicts["configuration_data"]
if size < cfg.LuaSharedDicts["certificate_data"] { if size < cfg.LuaSharedDicts["certificate_data"] {
size = cfg.LuaSharedDicts["certificate_data"] size = cfg.LuaSharedDicts["certificate_data"]
} }
size = size + 1 size = size + 1024
return fmt.Sprintf("%d", size) return dictKbToStr(size)
} }
// configForLua returns some general configuration as Lua table represented as string // configForLua returns some general configuration as Lua table represented as string

View file

@ -214,7 +214,7 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
// config lua dict // config lua dict
cfg := config.Configuration{ cfg := config.Configuration{
LuaSharedDicts: map[string]int{ LuaSharedDicts: map[string]int{
"configuration_data": 10, "certificate_data": 20, "configuration_data": 10240, "certificate_data": 20480,
}, },
} }
actual := buildLuaSharedDictionaries(cfg, invalidType) actual := buildLuaSharedDictionaries(cfg, invalidType)
@ -255,13 +255,13 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
func TestLuaConfigurationRequestBodySize(t *testing.T) { func TestLuaConfigurationRequestBodySize(t *testing.T) {
cfg := config.Configuration{ cfg := config.Configuration{
LuaSharedDicts: map[string]int{ LuaSharedDicts: map[string]int{
"configuration_data": 10, "certificate_data": 20, "configuration_data": 10240, "certificate_data": 20480,
}, },
} }
size := luaConfigurationRequestBodySize(cfg) size := luaConfigurationRequestBodySize(cfg)
if size != "21" { if size != "21M" {
t.Errorf("expected the size to be 20 but got: %v", size) t.Errorf("expected the size to be 21M but got: %v", size)
} }
} }

View file

@ -676,8 +676,8 @@ http {
} }
location /configuration { location /configuration {
client_max_body_size {{ luaConfigurationRequestBodySize $cfg }}m; client_max_body_size {{ luaConfigurationRequestBodySize $cfg }};
client_body_buffer_size {{ luaConfigurationRequestBodySize $cfg }}m; client_body_buffer_size {{ luaConfigurationRequestBodySize $cfg }};
proxy_buffering off; proxy_buffering off;
content_by_lua_block { content_by_lua_block {