feat(config): Add fields for controller rate limit configuration
This commit is contained in:
5 files changed
+303
-4
No files matched your search
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/event"
|
||||
"github.com/hashicorp/boundary/internal/ratelimit"
|
||||
"github.com/hashicorp/boundary/internal/util"
|
||||
kms_plugin_assets "github.com/hashicorp/boundary/plugins/kms"
|
||||
"github.com/hashicorp/boundary/sdk/wrapper"
|
||||
@@ -214,6 +215,9 @@ type Controller struct {
|
||||
// TODO: This field is currently internal.
|
||||
SchedulerRunJobInterval time.Duration `hcl:"-"`
|
||||
|
||||
ApiRateLimits ratelimit.Configs `hcl:"-"`
|
||||
ApiRateLimiterMaxEntries int `hcl:"api_rate_limit_max_entries"`
|
||||
|
||||
// License is the license used by HCP builds
|
||||
License string `hcl:"license"`
|
||||
}
|
||||
@@ -682,6 +686,15 @@ func Parse(d string) (*Config, error) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result.Controller.ApiRateLimits, err = parseApiRateLimits(obj.Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Controller.ApiRateLimiterMaxEntries <= 0 {
|
||||
result.Controller.ApiRateLimiterMaxEntries = ratelimit.DefaultLimiterMaxEntries
|
||||
}
|
||||
}
|
||||
|
||||
// Parse worker tags
|
||||
@@ -944,6 +957,38 @@ func supportControllersRawConfig(initialUpstreamsRaw, controllersRaw any) (any,
|
||||
return initialUpstreamsRaw, nil
|
||||
}
|
||||
|
||||
func parseApiRateLimits(node ast.Node) (ratelimit.Configs, error) {
|
||||
list, ok := node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
|
||||
}
|
||||
controllerList := list.Filter("controller")
|
||||
|
||||
configs := make(ratelimit.Configs, 0)
|
||||
for _, item := range controllerList.Items {
|
||||
controller, ok := item.Val.(*ast.ObjectType)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing: file doesn't contain controller object")
|
||||
}
|
||||
apiRateLimitsList := controller.List.Filter("api_rate_limit")
|
||||
|
||||
var err error
|
||||
for i, item := range apiRateLimitsList.Items {
|
||||
var a ratelimit.Config
|
||||
if err := hcl.DecodeObject(&a, item.Val); err != nil {
|
||||
return nil, fmt.Errorf("error decoding controller api_rate_limit entry %d", i)
|
||||
}
|
||||
a.Period, err = parseutil.ParseDurationSecond(a.PeriodHCL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding controller api_rate_limit period for entry %d", i)
|
||||
}
|
||||
configs = append(configs, &a)
|
||||
}
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func parseWorkerUpstreams(c *Config) ([]string, error) {
|
||||
if c == nil || c.Worker == nil {
|
||||
return nil, fmt.Errorf("config or worker field is nil")
|
||||
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/event"
|
||||
"github.com/hashicorp/boundary/internal/ratelimit"
|
||||
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
|
||||
"github.com/hashicorp/go-secure-stdlib/listenerutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -416,6 +418,8 @@ func TestLoad(t *testing.T) {
|
||||
GracefulShutdownWaitDuration: 0,
|
||||
WorkerStatusGracePeriodDuration: 0,
|
||||
LivenessTimeToStaleDuration: 0,
|
||||
ApiRateLimits: make(ratelimit.Configs, 0),
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: false,
|
||||
DevUiPassthroughDir: "",
|
||||
@@ -839,6 +843,8 @@ func TestLoad(t *testing.T) {
|
||||
GracefulShutdownWaitDuration: 0,
|
||||
WorkerStatusGracePeriodDuration: 0,
|
||||
LivenessTimeToStaleDuration: 0,
|
||||
ApiRateLimits: make(ratelimit.Configs, 0),
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: false,
|
||||
DevUiPassthroughDir: "",
|
||||
@@ -1257,6 +1263,27 @@ func TestLoad(t *testing.T) {
|
||||
GracefulShutdownWaitDuration: 0,
|
||||
WorkerStatusGracePeriodDuration: 0,
|
||||
LivenessTimeToStaleDuration: 0,
|
||||
ApiRateLimits: ratelimit.Configs{
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"*"},
|
||||
Per: "total",
|
||||
Limit: 50,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"list"},
|
||||
Per: "total",
|
||||
Limit: 20,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
},
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: false,
|
||||
DevUiPassthroughDir: "",
|
||||
@@ -1675,6 +1702,8 @@ func TestLoad(t *testing.T) {
|
||||
GracefulShutdownWaitDuration: 0,
|
||||
WorkerStatusGracePeriodDuration: 0,
|
||||
LivenessTimeToStaleDuration: 0,
|
||||
ApiRateLimits: make(ratelimit.Configs, 0),
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: false,
|
||||
DevUiPassthroughDir: "",
|
||||
@@ -1764,6 +1793,8 @@ func TestLoad(t *testing.T) {
|
||||
GracefulShutdownWaitDuration: 0,
|
||||
WorkerStatusGracePeriodDuration: 0,
|
||||
LivenessTimeToStaleDuration: 0,
|
||||
ApiRateLimits: make(ratelimit.Configs, 0),
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: false,
|
||||
DevUiPassthroughDir: "",
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/event"
|
||||
"github.com/hashicorp/boundary/internal/ratelimit"
|
||||
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
|
||||
"github.com/hashicorp/go-secure-stdlib/listenerutil"
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
@@ -109,8 +110,10 @@ func TestDevController(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Controller: &Controller{
|
||||
Name: "dev-controller",
|
||||
Description: "A default controller created in dev mode",
|
||||
Name: "dev-controller",
|
||||
Description: "A default controller created in dev mode",
|
||||
ApiRateLimits: make(ratelimit.Configs, 0),
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: true,
|
||||
}
|
||||
@@ -482,8 +485,10 @@ func TestDevCombined(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Controller: &Controller{
|
||||
Name: "dev-controller",
|
||||
Description: "A default controller created in dev mode",
|
||||
Name: "dev-controller",
|
||||
Description: "A default controller created in dev mode",
|
||||
ApiRateLimits: make(ratelimit.Configs, 0),
|
||||
ApiRateLimiterMaxEntries: ratelimit.DefaultLimiterMaxEntries,
|
||||
},
|
||||
DevController: true,
|
||||
Worker: &Worker{
|
||||
@@ -1549,6 +1554,208 @@ func TestControllerDescription(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestControllerApiRateLimits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
expLimits ratelimit.Configs
|
||||
expErr bool
|
||||
expErrStr string
|
||||
}{
|
||||
{
|
||||
name: "Single Rate limit",
|
||||
in: `
|
||||
controller {
|
||||
api_rate_limit {
|
||||
resources = ["*"]
|
||||
actions = ["*"]
|
||||
per = "total"
|
||||
limit = 50
|
||||
period = "1m"
|
||||
}
|
||||
}`,
|
||||
expLimits: ratelimit.Configs{
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"*"},
|
||||
Per: "total",
|
||||
Limit: 50,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "Single Rate with name",
|
||||
in: `
|
||||
controller {
|
||||
api_rate_limit "default" {
|
||||
resources = ["*"]
|
||||
actions = ["*"]
|
||||
per = "total"
|
||||
limit = 50
|
||||
period = "1m"
|
||||
}
|
||||
}`,
|
||||
expLimits: ratelimit.Configs{
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"*"},
|
||||
Per: "total",
|
||||
Limit: 50,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple Rate limit",
|
||||
in: `
|
||||
controller {
|
||||
api_rate_limit {
|
||||
resources = ["*"]
|
||||
actions = ["*"]
|
||||
per = "total"
|
||||
limit = 50
|
||||
period = "1m"
|
||||
}
|
||||
|
||||
api_rate_limit {
|
||||
resources = ["*"]
|
||||
actions = ["list"]
|
||||
per = "total"
|
||||
limit = 20
|
||||
period = "1m"
|
||||
}
|
||||
}`,
|
||||
expLimits: ratelimit.Configs{
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"*"},
|
||||
Per: "total",
|
||||
Limit: 50,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"list"},
|
||||
Per: "total",
|
||||
Limit: 20,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple Rate limit with names",
|
||||
in: `
|
||||
controller {
|
||||
api_rate_limit "default" {
|
||||
resources = ["*"]
|
||||
actions = ["*"]
|
||||
per = "total"
|
||||
limit = 50
|
||||
period = "1m"
|
||||
}
|
||||
|
||||
api_rate_limit "default-list" {
|
||||
resources = ["*"]
|
||||
actions = ["list"]
|
||||
per = "total"
|
||||
limit = 20
|
||||
period = "1m"
|
||||
}
|
||||
}`,
|
||||
expLimits: ratelimit.Configs{
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"*"},
|
||||
Per: "total",
|
||||
Limit: 50,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"list"},
|
||||
Per: "total",
|
||||
Limit: 20,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple Rate limit with name and no name",
|
||||
in: `
|
||||
controller {
|
||||
api_rate_limit {
|
||||
resources = ["*"]
|
||||
actions = ["*"]
|
||||
per = "total"
|
||||
limit = 50
|
||||
period = "1m"
|
||||
}
|
||||
|
||||
api_rate_limit "list" {
|
||||
resources = ["*"]
|
||||
actions = ["list"]
|
||||
per = "total"
|
||||
limit = 20
|
||||
period = "1m"
|
||||
}
|
||||
}`,
|
||||
expLimits: ratelimit.Configs{
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"*"},
|
||||
Per: "total",
|
||||
Limit: 50,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
{
|
||||
Resources: []string{"*"},
|
||||
Actions: []string{"list"},
|
||||
Per: "total",
|
||||
Limit: 20,
|
||||
PeriodHCL: "1m",
|
||||
Period: time.Minute,
|
||||
Unlimited: false,
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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.expLimits, c.Controller.ApiRateLimits)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkerDescription(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
+8
@@ -10,6 +10,14 @@ controller {
|
||||
database {
|
||||
url = "env://LOAD_TEST_BOUNDARY_POSTGRES_URL"
|
||||
}
|
||||
|
||||
api_rate_limit {
|
||||
resources = ["*"]
|
||||
actions = ["*"]
|
||||
per = "total"
|
||||
limit = 50
|
||||
period = "1m"
|
||||
}
|
||||
}
|
||||
|
||||
listener "tcp" {
|
||||
|
||||
Vendored
+8
@@ -8,4 +8,12 @@ controller {
|
||||
database {
|
||||
url = "env://LOAD_TEST_BOUNDARY_POSTGRES_URL"
|
||||
}
|
||||
|
||||
api_rate_limit {
|
||||
resources = ["*"]
|
||||
actions = ["list"]
|
||||
per = "total"
|
||||
limit = 20
|
||||
period = "1m"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user