feat(config): Add fields for controller rate limit configuration

This commit is contained in:
Timothy Messier committed 2023-12-04 16:20:28 +00:00
1 parent 0fb77358e5
commit bee45dc486
5 files changed
+303 -4

No files matched your search

+45
View File
@@ -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")
+31
View File
@@ -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: "",
+211 -4
View File
@@ -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
@@ -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" {
@@ -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"
}
}