config: add max_page_size variable

The max_page_size variable lets operators control the size of the
pagination pages. This value configures both the maximum permitted
page size and the default page size when none is explicitly provided.

Operators can tweak this option up or down to increase or reduce
the load on the database during pagination.
pull/4202/head
Johan Brandhorst-Satzkorn 3 years ago
parent 5bfc8a88b1
commit 9ce5a14ef0

@ -221,6 +221,14 @@ type Controller struct {
// License is the license used by HCP builds
License string `hcl:"license"`
// MaxPageSize overrides the default and max page size.
// The default page size is what is used when the page size
// is not explicitly provided by the user. The max page size
// is the greatest number the page size can be set to before
// it is rejected by the controller.
MaxPageSizeRaw any `hcl:"max_page_size"`
MaxPageSize uint `hcl:"-"`
}
func (c *Controller) InitNameIfEmpty(ctx context.Context) error {
@ -630,6 +638,31 @@ func Parse(d string) (*Config, error) {
return nil, errors.New("Controller liveness time to stale value is negative")
}
if result.Controller.MaxPageSizeRaw != nil {
switch t := result.Controller.MaxPageSizeRaw.(type) {
case string:
maxPageSizeString, err := parseutil.ParsePath(t)
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return nil, fmt.Errorf("Error parsing max page size: %w", err)
}
pageSize, err := strconv.Atoi(maxPageSizeString)
if err != nil {
return nil, fmt.Errorf("Max page size value is not an int: %w", err)
}
if pageSize <= 0 {
return nil, fmt.Errorf("Max page size value must be at least 1, was %d", pageSize)
}
result.Controller.MaxPageSize = uint(pageSize)
case int:
if t <= 0 {
return nil, fmt.Errorf("Max page size value must be at least 1, was %d", t)
}
result.Controller.MaxPageSize = uint(t)
default:
return nil, fmt.Errorf("Max page size: unsupported type %q", reflect.TypeOf(t).String())
}
}
if result.Controller.Database != nil {
if result.Controller.Database.MaxOpenConnectionsRaw != nil {
switch t := result.Controller.Database.MaxOpenConnectionsRaw.(type) {
@ -685,7 +718,6 @@ func Parse(d string) (*Config, error) {
reflect.TypeOf(t).String())
}
}
}
result.Controller.ApiRateLimits, err = parseApiRateLimits(obj.Node)

@ -420,6 +420,8 @@ func TestLoad(t *testing.T) {
LivenessTimeToStaleDuration: 0,
ApiRateLimits: make(ratelimit.Configs, 0),
ApiRateLimiterMaxQuotas: ratelimit.DefaultLimiterMaxQuotas(),
MaxPageSizeRaw: nil,
MaxPageSize: 0,
},
DevController: false,
DevUiPassthroughDir: "",
@ -845,6 +847,8 @@ func TestLoad(t *testing.T) {
LivenessTimeToStaleDuration: 0,
ApiRateLimits: make(ratelimit.Configs, 0),
ApiRateLimiterMaxQuotas: ratelimit.DefaultLimiterMaxQuotas(),
MaxPageSizeRaw: nil,
MaxPageSize: 0,
},
DevController: false,
DevUiPassthroughDir: "",
@ -1284,6 +1288,8 @@ func TestLoad(t *testing.T) {
},
},
ApiRateLimiterMaxQuotas: ratelimit.DefaultLimiterMaxQuotas(),
MaxPageSizeRaw: nil,
MaxPageSize: 0,
},
DevController: false,
DevUiPassthroughDir: "",
@ -1704,6 +1710,8 @@ func TestLoad(t *testing.T) {
LivenessTimeToStaleDuration: 0,
ApiRateLimits: make(ratelimit.Configs, 0),
ApiRateLimiterMaxQuotas: ratelimit.DefaultLimiterMaxQuotas(),
MaxPageSizeRaw: nil,
MaxPageSize: 0,
},
DevController: false,
DevUiPassthroughDir: "",
@ -1795,6 +1803,8 @@ func TestLoad(t *testing.T) {
LivenessTimeToStaleDuration: 0,
ApiRateLimits: make(ratelimit.Configs, 0),
ApiRateLimiterMaxQuotas: ratelimit.DefaultLimiterMaxQuotas(),
MaxPageSizeRaw: nil,
MaxPageSize: 0,
},
DevController: false,
DevUiPassthroughDir: "",

@ -2683,3 +2683,105 @@ func TestSetupWorkerInitialUpstreams(t *testing.T) {
})
}
}
func TestMaxPageSize(t *testing.T) {
tests := []struct {
name string
in string
envMaxPageSize string
expMaxPageSize uint
expErr bool
expErrStr string
}{
{
name: "Valid integer value",
in: `
controller {
name = "example-controller"
max_page_size = 5
}`,
expMaxPageSize: 5,
expErr: false,
},
{
name: "Valid string value",
in: `
controller {
name = "example-controller"
max_page_size = "5"
}`,
expMaxPageSize: 5,
expErr: false,
},
{
name: "Invalid value integer",
in: `
controller {
name = "example-controller"
max_page_size = 0
}`,
expErr: true,
expErrStr: "Max page size value must be at least 1, was 0",
},
{
name: "Invalid value string",
in: `
controller {
name = "example-controller"
max_page_size = "string bad"
}`,
expErr: true,
expErrStr: "Max page size value is not an int: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax",
},
{
name: "Invalid value type",
in: `
controller {
name = "example-controller"
max_page_size = false
}`,
expErr: true,
expErrStr: "Max page size: unsupported type \"bool\"",
},
{
name: "Valid env var",
in: `
controller {
name = "example-controller"
max_page_size = "env://ENV_MAX_CONN"
}`,
expMaxPageSize: 8,
envMaxPageSize: "8",
expErr: false,
},
{
name: "Invalid env var",
in: `
controller {
name = "example-controller"
max_page_size = "env://ENV_MAX_CONN"
}`,
envMaxPageSize: "bogus value",
expErr: true,
expErrStr: "Max page size value is not an int: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ENV_MAX_CONN", tt.envMaxPageSize)
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Controller)
require.Equal(t, tt.expMaxPageSize, c.Controller.MaxPageSize)
})
}
}

@ -139,6 +139,10 @@ The `api_rate_limit` configuration stanza contains the following fields:
If `api_rate_limit_disable` is set to `true`, and you have provided any `api_rate_limit` stanzas, you will receive an error.
- `api_rate_limit_max_quotas` - Specifies the maximum number of API rate limiting quotas that Boundary allows.
- `max_page_size` - The max allowed page size when paginating. If a user specifies a page size greater than
this number, it will be truncated to this number. This is also used as the default page size for any request
that don't explicitly specify a page size. Default is 1000.
## Signals
The `SIGHUP` signal causes a controller to reload its configuration file to pick up any updates to the `database url` value. Any other updated values are ignored.

Loading…
Cancel
Save