From a99b50ecfa3235bfe188ef84c519663b44b6676a Mon Sep 17 00:00:00 2001 From: guangwu Date: Wed, 8 May 2024 18:10:01 +0800 Subject: [PATCH 001/117] fix: close file Signed-off-by: guangwu --- drives/sync/filesystem.go | 1 + 1 file changed, 1 insertion(+) diff --git a/drives/sync/filesystem.go b/drives/sync/filesystem.go index 153f929c..b5d721b0 100644 --- a/drives/sync/filesystem.go +++ b/drives/sync/filesystem.go @@ -124,6 +124,7 @@ func (t *FilesystemTarget) WriteStream(path string, stream io.Reader, mode os.Fi if err != nil { return err } + defer f.Close() _, err = io.Copy(f, stream) if err != nil { return err From 017773f816cb6f4e4c5f8bdf8f7820c99ed3638c Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 13 May 2024 11:57:33 -0400 Subject: [PATCH 002/117] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b36a97df..57436747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.31 + +FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606) + ## v0.4.30 FIX: Fix to the Node.js release process to properly support releasing on a tag. From 5d72890d320d5a01da180edb6c450951d7ba4c1a Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 13 May 2024 12:36:00 -0400 Subject: [PATCH 003/117] limits ddl for postgres (#606) --- .../postgresql/022_v0_4_31_limits_classes.sql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql diff --git a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql b/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql new file mode 100644 index 00000000..71841f16 --- /dev/null +++ b/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql @@ -0,0 +1,19 @@ +-- +migrate Up + +create type limit_scope as enum ('account', 'environment', 'share'); +create type limit_action as enum ('warning', 'limit'); + +create table limits_classes ( + id serial primary key, + limit_scope limit_scope not null default ('account'), + limit_action limit_action not null default ('limit'), + share_mode share_mode, + backend_mode backend_mode, + period_minutes int not null default (1440), + rx_bytes bigint not null default (-1), + tx_bytes bigint not null default (-1), + total_bytes bigint not null default (-1), + created_at timestamptz not null default(current_timestamp), + updated_at timestamptz not null default(current_timestamp), + deleted boolean not null default(false) +) \ No newline at end of file From 1238c4981c27761ec5a254951379994ab5ef1e9d Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 13 May 2024 12:39:30 -0400 Subject: [PATCH 004/117] limits ddl for sqlite (#606) --- .../sql/sqlite3/022_v0_4_31_limits_classes.sql | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql diff --git a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql new file mode 100644 index 00000000..70792f1e --- /dev/null +++ b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql @@ -0,0 +1,16 @@ +-- +migrate Up + +create table limits_classes ( + id serial primary key, + limit_scope string not null default ('account'), + limit_action string not null default ('limit'), + share_mode string, + backend_mode string, + period_minutes int not null default (1440), + rx_bytes bigint not null default (-1), + tx_bytes bigint not null default (-1), + total_bytes bigint not null default (-1), + created_at timestamptz not null default(current_timestamp), + updated_at timestamptz not null default(current_timestamp), + deleted boolean not null default(false) +) \ No newline at end of file From 4d67598acb6447cf46cc0b305f12e68896253f4d Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 13 May 2024 12:50:14 -0400 Subject: [PATCH 005/117] applied_limit_classes (#606) --- .../postgresql/022_v0_4_31_limits_classes.sql | 13 +++++++++++-- .../sqlite3/022_v0_4_31_limits_classes.sql | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql b/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql index 71841f16..426b80be 100644 --- a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql @@ -3,7 +3,7 @@ create type limit_scope as enum ('account', 'environment', 'share'); create type limit_action as enum ('warning', 'limit'); -create table limits_classes ( +create table limit_classes ( id serial primary key, limit_scope limit_scope not null default ('account'), limit_action limit_action not null default ('limit'), @@ -16,4 +16,13 @@ create table limits_classes ( created_at timestamptz not null default(current_timestamp), updated_at timestamptz not null default(current_timestamp), deleted boolean not null default(false) -) \ No newline at end of file +); + +create table applied_limit_classes ( + id serial primary key, + account_id integer not null references accounts (id), + limit_class_id integer not null references limit_classes (id), + created_at timestamptz not null default(current_timestamp), + updated_at timestamptz not null default(current_timestamp), + deleted boolean not null default(false) +); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql index 70792f1e..78efd7ac 100644 --- a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql @@ -1,16 +1,25 @@ -- +migrate Up -create table limits_classes ( +create table limit_classes ( id serial primary key, limit_scope string not null default ('account'), limit_action string not null default ('limit'), share_mode string, backend_mode string, - period_minutes int not null default (1440), + period_minutes integer not null default (1440), rx_bytes bigint not null default (-1), tx_bytes bigint not null default (-1), total_bytes bigint not null default (-1), - created_at timestamptz not null default(current_timestamp), - updated_at timestamptz not null default(current_timestamp), + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), deleted boolean not null default(false) -) \ No newline at end of file +); + +create table applied_limit_classes ( + id serial primary key, + account_id integer not null references accounts (id), + limit_class_id integer not null references limit_classes (id), + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + deleted boolean not null default(false) +); \ No newline at end of file From 75d58e84e43b26c37d97c9ec53b34f2ac08a91b2 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 14 May 2024 13:24:48 -0400 Subject: [PATCH 006/117] LimitJournalAction -> LimitAction; LimitScope (#606) --- controller/limits/agent.go | 48 ++++++++++---------- controller/overview.go | 6 +-- controller/store/accountLimitJournal.go | 2 +- controller/store/accountLimitJournal_test.go | 16 +++---- controller/store/environmentLimitJournal.go | 2 +- controller/store/model.go | 16 +++++-- controller/store/shareLimitJournal.go | 2 +- 7 files changed, 50 insertions(+), 42 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 8df3ecb4..97455618 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -71,7 +71,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if alj.Action == store.LimitAction { + if alj.Action == store.LimitLimitAction { return false, nil } } else if err != nil { @@ -98,7 +98,7 @@ func (a *Agent) CanCreateShare(acctId, envId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if alj.Action == store.LimitAction { + if alj.Action == store.LimitLimitAction { return false, nil } } else if err != nil { @@ -110,7 +110,7 @@ func (a *Agent) CanCreateShare(acctId, envId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if elj.Action == store.LimitAction { + if elj.Action == store.LimitLimitAction { return false, nil } } else if err != nil { @@ -150,7 +150,7 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if slj.Action == store.LimitAction { + if slj.Action == store.LimitLimitAction { return false, nil } } else if err != nil { @@ -166,7 +166,7 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if elj.Action == store.LimitAction { + if elj.Action == store.LimitLimitAction { return false, nil } } else if err != nil { @@ -183,7 +183,7 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if alj.Action == store.LimitAction { + if alj.Action == store.LimitLimitAction { return false, nil } } else if err != nil { @@ -257,7 +257,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { var enforcedAt time.Time if empty, err := a.str.IsAccountLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { if latest, err := a.str.FindLatestAccountLimitJournal(int(u.AccountId), trx); err == nil { - enforced = latest.Action == store.LimitAction + enforced = latest.Action == store.LimitLimitAction enforcedAt = latest.UpdatedAt } } @@ -267,7 +267,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { AccountId: int(u.AccountId), RxBytes: rxBytes, TxBytes: txBytes, - Action: store.LimitAction, + Action: store.LimitLimitAction, }, trx) if err != nil { return err @@ -294,7 +294,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { var warnedAt time.Time if empty, err := a.str.IsAccountLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { if latest, err := a.str.FindLatestAccountLimitJournal(int(u.AccountId), trx); err == nil { - warned = latest.Action == store.WarningAction || latest.Action == store.LimitAction + warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction warnedAt = latest.UpdatedAt } } @@ -304,7 +304,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { AccountId: int(u.AccountId), RxBytes: rxBytes, TxBytes: txBytes, - Action: store.WarningAction, + Action: store.WarningLimitAction, }, trx) if err != nil { return err @@ -333,7 +333,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { var enforcedAt time.Time if empty, err := a.str.IsEnvironmentLimitJournalEmpty(int(u.EnvironmentId), trx); err == nil && !empty { if latest, err := a.str.FindLatestEnvironmentLimitJournal(int(u.EnvironmentId), trx); err == nil { - enforced = latest.Action == store.LimitAction + enforced = latest.Action == store.LimitLimitAction enforcedAt = latest.UpdatedAt } } @@ -343,7 +343,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { EnvironmentId: int(u.EnvironmentId), RxBytes: rxBytes, TxBytes: txBytes, - Action: store.LimitAction, + Action: store.LimitLimitAction, }, trx) if err != nil { return err @@ -370,7 +370,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { var warnedAt time.Time if empty, err := a.str.IsEnvironmentLimitJournalEmpty(int(u.EnvironmentId), trx); err == nil && !empty { if latest, err := a.str.FindLatestEnvironmentLimitJournal(int(u.EnvironmentId), trx); err == nil { - warned = latest.Action == store.WarningAction || latest.Action == store.LimitAction + warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction warnedAt = latest.UpdatedAt } } @@ -380,7 +380,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { EnvironmentId: int(u.EnvironmentId), RxBytes: rxBytes, TxBytes: txBytes, - Action: store.WarningAction, + Action: store.WarningLimitAction, }, trx) if err != nil { return err @@ -414,7 +414,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { var enforcedAt time.Time if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty { if latest, err := a.str.FindLatestShareLimitJournal(shr.Id, trx); err == nil { - enforced = latest.Action == store.LimitAction + enforced = latest.Action == store.LimitLimitAction enforcedAt = latest.UpdatedAt } } @@ -424,7 +424,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { ShareId: shr.Id, RxBytes: rxBytes, TxBytes: txBytes, - Action: store.LimitAction, + Action: store.LimitLimitAction, }, trx) if err != nil { return err @@ -452,7 +452,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { var warnedAt time.Time if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty { if latest, err := a.str.FindLatestShareLimitJournal(shr.Id, trx); err == nil { - warned = latest.Action == store.WarningAction || latest.Action == store.LimitAction + warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction warnedAt = latest.UpdatedAt } } @@ -462,7 +462,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { ShareId: shr.Id, RxBytes: rxBytes, TxBytes: txBytes, - Action: store.WarningAction, + Action: store.WarningLimitAction, }, trx) if err != nil { return err @@ -509,10 +509,10 @@ func (a *Agent) relax() error { if sljs, err := a.str.FindAllLatestShareLimitJournal(trx); err == nil { for _, slj := range sljs { if shr, err := a.str.GetShare(slj.ShareId, trx); err == nil { - if slj.Action == store.WarningAction || slj.Action == store.LimitAction { + if slj.Action == store.WarningLimitAction || slj.Action == store.LimitLimitAction { if enforce, warning, rxBytes, txBytes, err := a.checkShareLimit(shr.Token); err == nil { if !enforce && !warning { - if slj.Action == store.LimitAction { + if slj.Action == store.LimitLimitAction { // run relax actions for share for _, action := range a.shrRelaxActions { if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil { @@ -545,10 +545,10 @@ func (a *Agent) relax() error { if eljs, err := a.str.FindAllLatestEnvironmentLimitJournal(trx); err == nil { for _, elj := range eljs { if env, err := a.str.GetEnvironment(elj.EnvironmentId, trx); err == nil { - if elj.Action == store.WarningAction || elj.Action == store.LimitAction { + if elj.Action == store.WarningLimitAction || elj.Action == store.LimitLimitAction { if enforce, warning, rxBytes, txBytes, err := a.checkEnvironmentLimit(int64(elj.EnvironmentId)); err == nil { if !enforce && !warning { - if elj.Action == store.LimitAction { + if elj.Action == store.LimitLimitAction { // run relax actions for environment for _, action := range a.envRelaxActions { if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil { @@ -581,10 +581,10 @@ func (a *Agent) relax() error { if aljs, err := a.str.FindAllLatestAccountLimitJournal(trx); err == nil { for _, alj := range aljs { if acct, err := a.str.GetAccount(alj.AccountId, trx); err == nil { - if alj.Action == store.WarningAction || alj.Action == store.LimitAction { + if alj.Action == store.WarningLimitAction || alj.Action == store.LimitLimitAction { if enforce, warning, rxBytes, txBytes, err := a.checkAccountLimit(int64(alj.AccountId)); err == nil { if !enforce && !warning { - if alj.Action == store.LimitAction { + if alj.Action == store.LimitLimitAction { // run relax actions for account for _, action := range a.acctRelaxActions { if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil { diff --git a/controller/overview.go b/controller/overview.go index a942cdaa..5291a6c8 100644 --- a/controller/overview.go +++ b/controller/overview.go @@ -127,7 +127,7 @@ func (h *overviewHandler) isAccountLimited(principal *rest_model_zrok.Principal, return false, err } } - return alj != nil && alj.Action == store.LimitAction, nil + return alj != nil && alj.Action == store.LimitLimitAction, nil } type sharesLimitedMap struct { @@ -145,7 +145,7 @@ func newSharesLimitedMap(shrs []*store.Share, trx *sqlx.Tx) (*sharesLimitedMap, } slm := &sharesLimitedMap{v: make(map[int]struct{})} for i := range shrsLimited { - if shrsLimited[i].Action == store.LimitAction { + if shrsLimited[i].Action == store.LimitLimitAction { slm.v[shrsLimited[i].ShareId] = struct{}{} } } @@ -172,7 +172,7 @@ func newEnvironmentsLimitedMap(envs []*store.Environment, trx *sqlx.Tx) (*enviro } elm := &environmentsLimitedMap{v: make(map[int]struct{})} for i := range envsLimited { - if envsLimited[i].Action == store.LimitAction { + if envsLimited[i].Action == store.LimitLimitAction { elm.v[envsLimited[i].EnvironmentId] = struct{}{} } } diff --git a/controller/store/accountLimitJournal.go b/controller/store/accountLimitJournal.go index 80194efa..2ac6ef83 100644 --- a/controller/store/accountLimitJournal.go +++ b/controller/store/accountLimitJournal.go @@ -10,7 +10,7 @@ type AccountLimitJournal struct { AccountId int RxBytes int64 TxBytes int64 - Action LimitJournalAction + Action LimitAction } func (str *Store) CreateAccountLimitJournal(j *AccountLimitJournal, trx *sqlx.Tx) (int, error) { diff --git a/controller/store/accountLimitJournal_test.go b/controller/store/accountLimitJournal_test.go index 88427dfc..add540e2 100644 --- a/controller/store/accountLimitJournal_test.go +++ b/controller/store/accountLimitJournal_test.go @@ -21,7 +21,7 @@ func TestAccountLimitJournal(t *testing.T) { acctId, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt", Password: "password", Token: "token", Limitless: false, Deleted: false}, trx) assert.Nil(t, err) - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 1024, TxBytes: 2048, Action: WarningAction}, trx) + _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 1024, TxBytes: 2048, Action: WarningLimitAction}, trx) assert.Nil(t, err) aljEmpty, err = str.IsAccountLimitJournalEmpty(acctId, trx) @@ -33,9 +33,9 @@ func TestAccountLimitJournal(t *testing.T) { assert.NotNil(t, latestAlj) assert.Equal(t, int64(1024), latestAlj.RxBytes) assert.Equal(t, int64(2048), latestAlj.TxBytes) - assert.Equal(t, WarningAction, latestAlj.Action) + assert.Equal(t, WarningLimitAction, latestAlj.Action) - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 2048, TxBytes: 4096, Action: LimitAction}, trx) + _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 2048, TxBytes: 4096, Action: LimitLimitAction}, trx) assert.Nil(t, err) latestAlj, err = str.FindLatestAccountLimitJournal(acctId, trx) @@ -43,7 +43,7 @@ func TestAccountLimitJournal(t *testing.T) { assert.NotNil(t, latestAlj) assert.Equal(t, int64(2048), latestAlj.RxBytes) assert.Equal(t, int64(4096), latestAlj.TxBytes) - assert.Equal(t, LimitAction, latestAlj.Action) + assert.Equal(t, LimitLimitAction, latestAlj.Action) } func TestFindAllLatestAccountLimitJournal(t *testing.T) { @@ -58,17 +58,17 @@ func TestFindAllLatestAccountLimitJournal(t *testing.T) { acctId1, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt1", Password: "password1", Token: "token1", Limitless: false, Deleted: false}, trx) assert.Nil(t, err) - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: WarningAction}, trx) + _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: WarningLimitAction}, trx) assert.Nil(t, err) - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: ClearAction}, trx) + _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: ClearLimitAction}, trx) assert.Nil(t, err) - aljId13, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: LimitAction}, trx) + aljId13, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: LimitLimitAction}, trx) assert.Nil(t, err) acctId2, err := str.CreateAccount(&Account{Email: "someone@somewhere.com", Salt: "salt2", Password: "password2", Token: "token2", Limitless: false, Deleted: false}, trx) assert.Nil(t, err) - aljId21, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId2, RxBytes: 2048, TxBytes: 4096, Action: WarningAction}, trx) + aljId21, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId2, RxBytes: 2048, TxBytes: 4096, Action: WarningLimitAction}, trx) assert.Nil(t, err) aljs, err := str.FindAllLatestAccountLimitJournal(trx) diff --git a/controller/store/environmentLimitJournal.go b/controller/store/environmentLimitJournal.go index b3b3d3cc..71d7c040 100644 --- a/controller/store/environmentLimitJournal.go +++ b/controller/store/environmentLimitJournal.go @@ -11,7 +11,7 @@ type EnvironmentLimitJournal struct { EnvironmentId int RxBytes int64 TxBytes int64 - Action LimitJournalAction + Action LimitAction } func (str *Store) CreateEnvironmentLimitJournal(j *EnvironmentLimitJournal, trx *sqlx.Tx) (int, error) { diff --git a/controller/store/model.go b/controller/store/model.go index dd9bd8b5..e2275123 100644 --- a/controller/store/model.go +++ b/controller/store/model.go @@ -1,11 +1,19 @@ package store -type LimitJournalAction string +type LimitAction string const ( - LimitAction LimitJournalAction = "limit" - WarningAction LimitJournalAction = "warning" - ClearAction LimitJournalAction = "clear" + LimitLimitAction LimitAction = "limit" + WarningLimitAction LimitAction = "warning" + ClearLimitAction LimitAction = "clear" +) + +type LimitScope string + +const ( + AccountLimitScope LimitScope = "account" + EnvironmentLimitScope LimitScope = "environment" + ShareLimitScope LimitScope = "share" ) type PermissionMode string diff --git a/controller/store/shareLimitJournal.go b/controller/store/shareLimitJournal.go index 2b935cc1..c0920694 100644 --- a/controller/store/shareLimitJournal.go +++ b/controller/store/shareLimitJournal.go @@ -11,7 +11,7 @@ type ShareLimitJournal struct { ShareId int RxBytes int64 TxBytes int64 - Action LimitJournalAction + Action LimitAction } func (str *Store) CreateShareLimitJournal(j *ShareLimitJournal, trx *sqlx.Tx) (int, error) { From bd5aaa061abf658de621fb3b94c0fafe0bbf5cd0 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 14 May 2024 14:16:20 -0400 Subject: [PATCH 007/117] the very basic store implementation for limit_classes, applied_limit_classes (#606) --- controller/store/appliedLimitClass.go | 40 +++++++++++++++++++++++++++ controller/store/limitClass.go | 31 +++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 controller/store/appliedLimitClass.go create mode 100644 controller/store/limitClass.go diff --git a/controller/store/appliedLimitClass.go b/controller/store/appliedLimitClass.go new file mode 100644 index 00000000..56c62aa2 --- /dev/null +++ b/controller/store/appliedLimitClass.go @@ -0,0 +1,40 @@ +package store + +import ( + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +type AppliedLimitClass struct { + Model + AccountId int + LimitClassId int +} + +func (str *Store) ApplyLimitClass(lc *AppliedLimitClass, trx *sqlx.Tx) (int, error) { + stmt, err := trx.Prepare("insert into applied_limit_classes (account_id, limit_class_id) values ($1, $2) returning id") + if err != nil { + return 0, errors.Wrap(err, "error preparing applied_limit_classes insert statement") + } + var id int + if err := stmt.QueryRow(lc.AccountId, lc.LimitClassId).Scan(&id); err != nil { + return 0, errors.Wrap(err, "error executing applied_limit_classes insert statement") + } + return id, nil +} + +func (str *Store) FindLimitClassesForAccount(acctId int, trx *sqlx.Tx) ([]*LimitClass, error) { + rows, err := trx.Queryx("select limit_classes.* from applied_limit_classes, limit_classes where applied_limit_classes.account_id = $1 and applied_limit_classes.limit_class_id = limit_classes.id", acctId) + if err != nil { + return nil, errors.Wrap(err, "error finding limit classes for account") + } + var lcs []*LimitClass + for rows.Next() { + lc := &LimitClass{} + if err := rows.StructScan(&lc); err != nil { + return nil, errors.Wrap(err, "error scanning limit_classes") + } + lcs = append(lcs, lc) + } + return lcs, nil +} diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go new file mode 100644 index 00000000..c79805eb --- /dev/null +++ b/controller/store/limitClass.go @@ -0,0 +1,31 @@ +package store + +import ( + "github.com/jmoiron/sqlx" + "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/pkg/errors" +) + +type LimitClass struct { + Model + LimitScope LimitScope + LimitAction LimitAction + ShareMode sdk.ShareMode + BackendMode sdk.BackendMode + PeriodMinutes int + RxBytes int64 + TxBytes int64 + TotalBytes int64 +} + +func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { + stmt, err := trx.Prepare("insert into limit_classes (limit_scope, limit_action, share_mode, backend_mode, period_minutes, rx_bytes, tx_bytes, total_bytes) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id") + if err != nil { + return 0, errors.Wrap(err, "error preparing limit_classes insert statement") + } + var id int + if err := stmt.QueryRow(lc.LimitScope, lc.LimitAction, lc.ShareMode, lc.BackendMode, lc.PeriodMinutes).Scan(&id); err != nil { + return 0, errors.Wrap(err, "error executing limit_classes insert statement") + } + return id, nil +} From f0b0a959f04a264147283fe4723a33d26495d7f4 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 15 May 2024 14:31:26 -0400 Subject: [PATCH 008/117] limit_check_lock (#287) --- controller/store/limitCheckLock.go | 17 +++++++++++++++++ .../023_v0_4_31_limit_check_locks.sql | 7 +++++++ controller/store/store.go | 5 +++-- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 controller/store/limitCheckLock.go create mode 100644 controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql diff --git a/controller/store/limitCheckLock.go b/controller/store/limitCheckLock.go new file mode 100644 index 00000000..e0e98137 --- /dev/null +++ b/controller/store/limitCheckLock.go @@ -0,0 +1,17 @@ +package store + +import ( + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +func (str *Store) LimitCheckLock(acctId int, trx *sqlx.Tx) error { + rows, err := trx.Queryx("select * from limit_check_locks where account_id = $1 for update", acctId) + if err != nil { + return errors.Wrap(err, "error preparing limit_check_locks select statement") + } + if !rows.Next() { + return errors.Errorf("no limit_check_locks entry for account_id '%d'", acctId) + } + return nil +} diff --git a/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql b/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql new file mode 100644 index 00000000..8f5ea3d8 --- /dev/null +++ b/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql @@ -0,0 +1,7 @@ +-- +migrate Up + +create table limit_check_locks ( + id serial primary key, + account_id integer not null references accounts (id), + updated_at timestamptz not null default(current_timestamp) +); \ No newline at end of file diff --git a/controller/store/store.go b/controller/store/store.go index f56e7bf5..6779344b 100644 --- a/controller/store/store.go +++ b/controller/store/store.go @@ -22,8 +22,9 @@ type Model struct { } type Config struct { - Path string `cf:"+secret"` - Type string + Path string `cf:"+secret"` + Type string + EnableLocking bool } type Store struct { From 2a770cc3b85c0445717fb695fbbfb3e169277199 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 15 May 2024 15:18:53 -0400 Subject: [PATCH 009/117] pessistic locking upsert on limit_check_locks (#287) --- controller/store/limitCheckLock.go | 8 ++++---- .../sql/postgresql/023_v0_4_31_limit_check_locks.sql | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/controller/store/limitCheckLock.go b/controller/store/limitCheckLock.go index e0e98137..59ad1b1a 100644 --- a/controller/store/limitCheckLock.go +++ b/controller/store/limitCheckLock.go @@ -6,12 +6,12 @@ import ( ) func (str *Store) LimitCheckLock(acctId int, trx *sqlx.Tx) error { - rows, err := trx.Queryx("select * from limit_check_locks where account_id = $1 for update", acctId) + stmt, err := trx.Prepare("insert into limit_check_locks (account_id) values ($1) on conflict (account_id) do update set updated_at = current_timestamp") if err != nil { - return errors.Wrap(err, "error preparing limit_check_locks select statement") + return errors.Wrap(err, "error preparing upsert on limit_check_locks") } - if !rows.Next() { - return errors.Errorf("no limit_check_locks entry for account_id '%d'", acctId) + if _, err := stmt.Exec(acctId); err != nil { + return errors.Wrap(err, "error executing upsert on limit_check_locks") } return nil } diff --git a/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql b/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql index 8f5ea3d8..0a225ecb 100644 --- a/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql +++ b/controller/store/sql/postgresql/023_v0_4_31_limit_check_locks.sql @@ -2,6 +2,6 @@ create table limit_check_locks ( id serial primary key, - account_id integer not null references accounts (id), + account_id integer not null references accounts (id) unique, updated_at timestamptz not null default(current_timestamp) ); \ No newline at end of file From af5afb8aac9d18c7aae23279bc3aaf7d00da57e1 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 15 May 2024 15:48:42 -0400 Subject: [PATCH 010/117] locking (#287) --- controller/limits/agent.go | 6 ++++++ controller/store/limitCheckLock.go | 14 ++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 97455618..9dd9332e 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -66,6 +66,9 @@ func (a *Agent) Stop() { func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { + if err := a.str.LimitCheckLock(acctId, trx); err != nil { + return false, err + } if empty, err := a.str.IsAccountLimitJournalEmpty(acctId, trx); err == nil && !empty { alj, err := a.str.FindLatestAccountLimitJournal(acctId, trx) if err != nil { @@ -93,6 +96,9 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { func (a *Agent) CanCreateShare(acctId, envId int, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { + if err := a.str.LimitCheckLock(acctId, trx); err != nil { + return false, err + } if empty, err := a.str.IsAccountLimitJournalEmpty(acctId, trx); err == nil && !empty { alj, err := a.str.FindLatestAccountLimitJournal(acctId, trx) if err != nil { diff --git a/controller/store/limitCheckLock.go b/controller/store/limitCheckLock.go index 59ad1b1a..edbae33b 100644 --- a/controller/store/limitCheckLock.go +++ b/controller/store/limitCheckLock.go @@ -6,12 +6,14 @@ import ( ) func (str *Store) LimitCheckLock(acctId int, trx *sqlx.Tx) error { - stmt, err := trx.Prepare("insert into limit_check_locks (account_id) values ($1) on conflict (account_id) do update set updated_at = current_timestamp") - if err != nil { - return errors.Wrap(err, "error preparing upsert on limit_check_locks") - } - if _, err := stmt.Exec(acctId); err != nil { - return errors.Wrap(err, "error executing upsert on limit_check_locks") + if str.cfg.EnableLocking { + stmt, err := trx.Prepare("insert into limit_check_locks (account_id) values ($1) on conflict (account_id) do update set updated_at = current_timestamp") + if err != nil { + return errors.Wrap(err, "error preparing upsert on limit_check_locks") + } + if _, err := stmt.Exec(acctId); err != nil { + return errors.Wrap(err, "error executing upsert on limit_check_locks") + } } return nil } From 7d18bea46c0204386fd0bad59502efda64929d11 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 21 May 2024 14:57:14 -0400 Subject: [PATCH 011/117] global limits for reserved shares and unique names (#632) --- controller/limits/config.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/controller/limits/config.go b/controller/limits/config.go index 86fea17e..412fa35a 100644 --- a/controller/limits/config.go +++ b/controller/limits/config.go @@ -5,11 +5,13 @@ import "time" const Unlimited = -1 type Config struct { - Environments int - Shares int - Bandwidth *BandwidthConfig - Cycle time.Duration - Enforcing bool + Environments int + Shares int + ReservedShares int + UniqueNames int + Bandwidth *BandwidthConfig + Cycle time.Duration + Enforcing bool } type BandwidthConfig struct { @@ -48,8 +50,10 @@ func DefaultBandwidthPerPeriod() *BandwidthPerPeriod { func DefaultConfig() *Config { return &Config{ - Environments: Unlimited, - Shares: Unlimited, + Environments: Unlimited, + Shares: Unlimited, + ReservedShares: Unlimited, + UniqueNames: Unlimited, Bandwidth: &BandwidthConfig{ PerAccount: DefaultBandwidthPerPeriod(), PerEnvironment: DefaultBandwidthPerPeriod(), From 94d49bacd5164fcac09afd0a3c78c1d76e24ef22 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 21 May 2024 15:09:12 -0400 Subject: [PATCH 012/117] example controller config (#632) --- etc/ctrl.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/ctrl.yml b/etc/ctrl.yml index e2c06c5d..f1abbe94 100644 --- a/etc/ctrl.yml +++ b/etc/ctrl.yml @@ -81,6 +81,8 @@ invites: limits: environments: -1 shares: -1 + reserved_shares: -1 + unique_names: -1 bandwidth: per_account: period: 5m From f23f2495ef9ee42122ba5dbbd3cc642a83af1832 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 21 May 2024 15:16:56 -0400 Subject: [PATCH 013/117] overrides for enviroments and shares (#606); overrides for reserved shares and unique names (#632) --- .../store/sql/postgresql/022_v0_4_31_limits_classes.sql | 7 +++++++ .../store/sql/sqlite3/022_v0_4_31_limits_classes.sql | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql b/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql index 426b80be..193626e2 100644 --- a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql @@ -5,14 +5,21 @@ create type limit_action as enum ('warning', 'limit'); create table limit_classes ( id serial primary key, + limit_scope limit_scope not null default ('account'), limit_action limit_action not null default ('limit'), share_mode share_mode, backend_mode backend_mode, + + environments int not null default (-1), + shares int not null default (-1), + reserved_shares int not null default (-1), + unique_names int not null default (-1), period_minutes int not null default (1440), rx_bytes bigint not null default (-1), tx_bytes bigint not null default (-1), total_bytes bigint not null default (-1), + created_at timestamptz not null default(current_timestamp), updated_at timestamptz not null default(current_timestamp), deleted boolean not null default(false) diff --git a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql index 78efd7ac..c8de0c00 100644 --- a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql @@ -2,14 +2,21 @@ create table limit_classes ( id serial primary key, + limit_scope string not null default ('account'), limit_action string not null default ('limit'), share_mode string, backend_mode string, + + environments integer not null default (-1), + shares integer not null default (-1), + reserved_shares integer not null default (-1), + unique_names integer not null default (-1), period_minutes integer not null default (1440), rx_bytes bigint not null default (-1), tx_bytes bigint not null default (-1), total_bytes bigint not null default (-1), + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), deleted boolean not null default(false) From 25a5bf6bd311cb23a73c799cde7257aa8000c4fd Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 22 May 2024 14:22:36 -0400 Subject: [PATCH 014/117] shares.unique_name for tracking unique names (#632) --- .../store/sql/postgresql/024_v0_4_31_unique_name_tracking.sql | 3 +++ .../store/sql/sqlite3/024_v0_4_31_unique_name_tracking.sql | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 controller/store/sql/postgresql/024_v0_4_31_unique_name_tracking.sql create mode 100644 controller/store/sql/sqlite3/024_v0_4_31_unique_name_tracking.sql diff --git a/controller/store/sql/postgresql/024_v0_4_31_unique_name_tracking.sql b/controller/store/sql/postgresql/024_v0_4_31_unique_name_tracking.sql new file mode 100644 index 00000000..0d12439a --- /dev/null +++ b/controller/store/sql/postgresql/024_v0_4_31_unique_name_tracking.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter table shares add column unique_name boolean not null default (false); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/024_v0_4_31_unique_name_tracking.sql b/controller/store/sql/sqlite3/024_v0_4_31_unique_name_tracking.sql new file mode 100644 index 00000000..0d12439a --- /dev/null +++ b/controller/store/sql/sqlite3/024_v0_4_31_unique_name_tracking.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter table shares add column unique_name boolean not null default (false); \ No newline at end of file From 9b03765086ce78f5a975c1d4680cc0a903d21fa0 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 22 May 2024 14:42:14 -0400 Subject: [PATCH 015/117] tracking unique names in the shares table (#632) --- controller/share.go | 1 + controller/store/share.go | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/controller/share.go b/controller/share.go index 8752a086..b2daabf7 100644 --- a/controller/share.go +++ b/controller/share.go @@ -147,6 +147,7 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr BackendMode: params.Body.BackendMode, BackendProxyEndpoint: ¶ms.Body.BackendProxyEndpoint, Reserved: reserved, + UniqueName: reserved && uniqueName != "", PermissionMode: store.OpenPermissionMode, } if params.Body.PermissionMode != "" { diff --git a/controller/store/share.go b/controller/store/share.go index 7005e603..305d76e3 100644 --- a/controller/store/share.go +++ b/controller/store/share.go @@ -16,17 +16,18 @@ type Share struct { FrontendEndpoint *string BackendProxyEndpoint *string Reserved bool + UniqueName bool PermissionMode PermissionMode Deleted bool } func (str *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) { - stmt, err := tx.Prepare("insert into shares (environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning id") + stmt, err := tx.Prepare("insert into shares (environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, reserved, unique_name, permission_mode) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing shares insert statement") } var id int - if err := stmt.QueryRow(envId, shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.PermissionMode).Scan(&id); err != nil { + if err := stmt.QueryRow(envId, shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.UniqueName, shr.PermissionMode).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing shares insert statement") } return id, nil @@ -97,12 +98,12 @@ func (str *Store) FindSharesForEnvironment(envId int, tx *sqlx.Tx) ([]*Share, er } func (str *Store) UpdateShare(shr *Share, tx *sqlx.Tx) error { - sql := "update shares set z_id = $1, token = $2, share_mode = $3, backend_mode = $4, frontend_selection = $5, frontend_endpoint = $6, backend_proxy_endpoint = $7, reserved = $8, permission_mode = $9, updated_at = current_timestamp where id = $10" + sql := "update shares set z_id = $1, token = $2, share_mode = $3, backend_mode = $4, frontend_selection = $5, frontend_endpoint = $6, backend_proxy_endpoint = $7, reserved = $8, unique_name = $9, permission_mode = $10, updated_at = current_timestamp where id = $11" stmt, err := tx.Prepare(sql) if err != nil { return errors.Wrap(err, "error preparing shares update statement") } - _, err = stmt.Exec(shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.PermissionMode, shr.Id) + _, err = stmt.Exec(shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.UniqueName, shr.PermissionMode, shr.Id) if err != nil { return errors.Wrap(err, "error executing shares update statement") } From 33fdce7337800ed0ecf902529575d03939a23271 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 22 May 2024 16:21:52 -0400 Subject: [PATCH 016/117] wire in checks for reserved share count and unique name count (#632) --- controller/limits/agent.go | 20 ++++++++++++++++++-- controller/share.go | 6 +++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 9dd9332e..619f6e28 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -94,7 +94,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { return true, nil } -func (a *Agent) CanCreateShare(acctId, envId int, trx *sqlx.Tx) (bool, error) { +func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err @@ -123,21 +123,37 @@ func (a *Agent) CanCreateShare(acctId, envId int, trx *sqlx.Tx) (bool, error) { return false, err } - if a.cfg.Shares > Unlimited { + if a.cfg.Shares > Unlimited || (reserved && a.cfg.ReservedShares > Unlimited) || (reserved && uniqueName && a.cfg.UniqueNames > Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err } total := 0 + reserveds := 0 + uniqueNames := 0 for i := range envs { shrs, err := a.str.FindSharesForEnvironment(envs[i].Id, trx) if err != nil { return false, errors.Wrapf(err, "unable to find shares for environment '%v'", envs[i].ZId) } total += len(shrs) + for _, shr := range shrs { + if shr.Reserved { + reserveds++ + } + if shr.UniqueName { + uniqueNames++ + } + } if total+1 > a.cfg.Shares { return false, nil } + if reserved && reserveds+1 > a.cfg.ReservedShares { + return false, nil + } + if reserved && uniqueName && uniqueNames+1 > a.cfg.UniqueNames { + return false, nil + } logrus.Infof("total = %d", total) } } diff --git a/controller/share.go b/controller/share.go index b2daabf7..7140be3e 100644 --- a/controller/share.go +++ b/controller/share.go @@ -49,7 +49,7 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr return share.NewShareInternalServerError() } - if err := h.checkLimits(envId, principal, trx); err != nil { + if err := h.checkLimits(envId, principal, params.Body.Reserved, params.Body.UniqueName != "", trx); err != nil { logrus.Errorf("limits error: %v", err) return share.NewShareUnauthorized() } @@ -190,10 +190,10 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr }) } -func (h *shareHandler) checkLimits(envId int, principal *rest_model_zrok.Principal, trx *sqlx.Tx) error { +func (h *shareHandler) checkLimits(envId int, principal *rest_model_zrok.Principal, reserved, uniqueName bool, trx *sqlx.Tx) error { if !principal.Limitless { if limitsAgent != nil { - ok, err := limitsAgent.CanCreateShare(int(principal.ID), envId, trx) + ok, err := limitsAgent.CanCreateShare(int(principal.ID), envId, reserved, uniqueName, trx) if err != nil { return errors.Wrapf(err, "error checking share limits for '%v'", principal.Email) } From 58eb8bfcca72c3735c84fdca48f11c578f8eb7a5 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 23 May 2024 11:50:55 -0400 Subject: [PATCH 017/117] debug logging for testing (#632) --- controller/limits/agent.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 619f6e28..02772b2d 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -146,12 +146,15 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, trx } } if total+1 > a.cfg.Shares { + logrus.Debugf("account '%d', environment '%d' over shares limit '%d'", acctId, envId, a.cfg.Shares) return false, nil } if reserved && reserveds+1 > a.cfg.ReservedShares { + logrus.Debugf("account '%v', environment '%d' over reserved shares limit '%d'", acctId, envId, a.cfg.ReservedShares) return false, nil } if reserved && uniqueName && uniqueNames+1 > a.cfg.UniqueNames { + logrus.Debugf("account '%v', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) return false, nil } logrus.Infof("total = %d", total) From 896a4a7845958b4617ea5430343310cfb5474f51 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 23 May 2024 14:08:14 -0400 Subject: [PATCH 018/117] limit class filtering (#606) --- controller/limits/agent.go | 13 ++++++++++- controller/limits/limitClasses.go | 38 +++++++++++++++++++++++++++++++ controller/share.go | 8 ++++--- controller/store/limitClass.go | 29 ++++++++++++++++------- 4 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 controller/limits/limitClasses.go diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 02772b2d..13321369 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -7,6 +7,7 @@ import ( "github.com/openziti/zrok/controller/metrics" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/controller/zrokEdgeSdk" + "github.com/openziti/zrok/sdk/golang/sdk" "github.com/openziti/zrok/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -94,7 +95,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { return true, nil } -func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, trx *sqlx.Tx) (bool, error) { +func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, shareMode sdk.ShareMode, backendMode sdk.BackendMode, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err @@ -123,6 +124,16 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, trx return false, err } + alc, err := a.str.FindLimitClassesForAccount(acctId, trx) + if err != nil { + logrus.Errorf("error finding limit classes for account with id '%d': %v", acctId, err) + return false, err + } + sortLimitClasses(alc) + if len(alc) > 0 { + logrus.Infof("selected limit class: %v", alc[0]) + } + if a.cfg.Shares > Unlimited || (reserved && a.cfg.ReservedShares > Unlimited) || (reserved && uniqueName && a.cfg.UniqueNames > Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { diff --git a/controller/limits/limitClasses.go b/controller/limits/limitClasses.go new file mode 100644 index 00000000..248b115e --- /dev/null +++ b/controller/limits/limitClasses.go @@ -0,0 +1,38 @@ +package limits + +import ( + "github.com/openziti/zrok/controller/store" + "sort" +) + +func sortLimitClasses(lcs []*store.LimitClass) { + sort.Slice(lcs, func(i, j int) bool { + ipoints := limitScopePoints(lcs[i]) + modePoints(lcs[i]) + jpoints := limitScopePoints(lcs[j]) + modePoints(lcs[j]) + return ipoints > jpoints + }) +} + +func limitScopePoints(lc *store.LimitClass) int { + points := 0 + switch lc.LimitScope { + case store.AccountLimitScope: + points += 1000 + case store.EnvironmentLimitScope: + points += 100 + case store.ShareLimitScope: + points += 10 + } + return points +} + +func modePoints(lc *store.LimitClass) int { + points := 0 + if lc.BackendMode != "" { + points += 1 + } + if lc.ShareMode != "" { + points += 1 + } + return points +} diff --git a/controller/share.go b/controller/share.go index 7140be3e..f3a244bf 100644 --- a/controller/share.go +++ b/controller/share.go @@ -49,7 +49,9 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr return share.NewShareInternalServerError() } - if err := h.checkLimits(envId, principal, params.Body.Reserved, params.Body.UniqueName != "", trx); err != nil { + shareMode := sdk.ShareMode(params.Body.ShareMode) + backendMode := sdk.BackendMode(params.Body.BackendMode) + if err := h.checkLimits(envId, principal, params.Body.Reserved, params.Body.UniqueName != "", shareMode, backendMode, trx); err != nil { logrus.Errorf("limits error: %v", err) return share.NewShareUnauthorized() } @@ -190,10 +192,10 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr }) } -func (h *shareHandler) checkLimits(envId int, principal *rest_model_zrok.Principal, reserved, uniqueName bool, trx *sqlx.Tx) error { +func (h *shareHandler) checkLimits(envId int, principal *rest_model_zrok.Principal, reserved, uniqueName bool, shareMode sdk.ShareMode, backendMode sdk.BackendMode, trx *sqlx.Tx) error { if !principal.Limitless { if limitsAgent != nil { - ok, err := limitsAgent.CanCreateShare(int(principal.ID), envId, reserved, uniqueName, trx) + ok, err := limitsAgent.CanCreateShare(int(principal.ID), envId, reserved, uniqueName, shareMode, backendMode, trx) if err != nil { return errors.Wrapf(err, "error checking share limits for '%v'", principal.Email) } diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index c79805eb..736bb44f 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -1,6 +1,7 @@ package store import ( + "encoding/json" "github.com/jmoiron/sqlx" "github.com/openziti/zrok/sdk/golang/sdk" "github.com/pkg/errors" @@ -8,14 +9,26 @@ import ( type LimitClass struct { Model - LimitScope LimitScope - LimitAction LimitAction - ShareMode sdk.ShareMode - BackendMode sdk.BackendMode - PeriodMinutes int - RxBytes int64 - TxBytes int64 - TotalBytes int64 + LimitScope LimitScope + LimitAction LimitAction + ShareMode sdk.ShareMode + BackendMode sdk.BackendMode + Shares int + ReservedShares int + UniqueNames int + PeriodMinutes int + RxBytes int64 + TxBytes int64 + TotalBytes int64 +} + +func (lc LimitClass) String() string { + out, err := json.MarshalIndent(&lc, "", " ") + if err != nil { + return "" + + } + return string(out) } func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { From 141cb424e0dce16e419e37042785071045d43fa4 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 30 May 2024 13:46:15 -0400 Subject: [PATCH 019/117] simplifying limit enforcement structures (#606) --- controller/limits/accountRelaxAction.go | 46 +++ controller/limits/agent.go | 296 +----------------- controller/limits/config.go | 18 +- controller/limits/environmentLimitAction.go | 41 --- controller/limits/environmentRelaxAction.go | 50 --- controller/limits/environmentWarningAction.go | 58 ---- controller/limits/shareLimitAction.go | 38 --- controller/limits/shareRelaxAction.go | 89 ------ controller/limits/shareWarningAction.go | 63 ---- 9 files changed, 55 insertions(+), 644 deletions(-) delete mode 100644 controller/limits/environmentLimitAction.go delete mode 100644 controller/limits/environmentRelaxAction.go delete mode 100644 controller/limits/environmentWarningAction.go delete mode 100644 controller/limits/shareLimitAction.go delete mode 100644 controller/limits/shareRelaxAction.go delete mode 100644 controller/limits/shareWarningAction.go diff --git a/controller/limits/accountRelaxAction.go b/controller/limits/accountRelaxAction.go index 8ad28354..d2b32380 100644 --- a/controller/limits/accountRelaxAction.go +++ b/controller/limits/accountRelaxAction.go @@ -2,6 +2,7 @@ package limits import ( "github.com/jmoiron/sqlx" + "github.com/openziti/edge-api/rest_management_api_client" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/controller/zrokEdgeSdk" "github.com/openziti/zrok/sdk/golang/sdk" @@ -53,3 +54,48 @@ func (a *accountRelaxAction) HandleAccount(acct *store.Account, _, _ int64, _ *B return nil } + +func relaxPublicShare(str *store.Store, edge *rest_management_api_client.ZitiEdgeManagement, shr *store.Share, trx *sqlx.Tx) error { + env, err := str.GetEnvironment(shr.EnvironmentId, trx) + if err != nil { + return errors.Wrap(err, "error finding environment") + } + + fe, err := str.FindFrontendPubliclyNamed(*shr.FrontendSelection, trx) + if err != nil { + return errors.Wrapf(err, "error finding frontend name '%v' for '%v'", *shr.FrontendSelection, shr.Token) + } + + if err := zrokEdgeSdk.CreateServicePolicyDial(env.ZId+"-"+shr.ZId+"-dial", shr.ZId, []string{fe.ZId}, zrokEdgeSdk.ZrokShareTags(shr.Token).SubTags, edge); err != nil { + return errors.Wrapf(err, "error creating dial service policy for '%v'", shr.Token) + } + logrus.Infof("added dial service policy for '%v'", shr.Token) + return nil +} + +func relaxPrivateShare(str *store.Store, edge *rest_management_api_client.ZitiEdgeManagement, shr *store.Share, trx *sqlx.Tx) error { + fes, err := str.FindFrontendsForPrivateShare(shr.Id, trx) + if err != nil { + return errors.Wrapf(err, "error finding frontends for share '%v'", shr.Token) + } + for _, fe := range fes { + if fe.EnvironmentId != nil { + env, err := str.GetEnvironment(*fe.EnvironmentId, trx) + if err != nil { + return errors.Wrapf(err, "error getting environment for frontend '%v'", fe.Token) + } + + addlTags := map[string]interface{}{ + "zrokEnvironmentZId": env.ZId, + "zrokFrontendToken": fe.Token, + "zrokShareToken": shr.Token, + } + if err := zrokEdgeSdk.CreateServicePolicyDial(fe.Token+"-"+env.ZId+"-"+shr.ZId+"-dial", shr.ZId, []string{env.ZId}, addlTags, edge); err != nil { + return errors.Wrapf(err, "unable to create dial policy for frontend '%v'", fe.Token) + } + + logrus.Infof("added dial service policy for share '%v' to private frontend '%v'", shr.Token, fe.Token) + } + } + return nil +} diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 13321369..6fddc103 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -24,12 +24,6 @@ type Agent struct { acctWarningActions []AccountAction acctLimitActions []AccountAction acctRelaxActions []AccountAction - envWarningActions []EnvironmentAction - envLimitActions []EnvironmentAction - envRelaxActions []EnvironmentAction - shrWarningActions []ShareAction - shrLimitActions []ShareAction - shrRelaxActions []ShareAction close chan struct{} join chan struct{} } @@ -44,12 +38,6 @@ func NewAgent(cfg *Config, ifxCfg *metrics.InfluxConfig, zCfg *zrokEdgeSdk.Confi acctWarningActions: []AccountAction{newAccountWarningAction(emailCfg, str)}, acctLimitActions: []AccountAction{newAccountLimitAction(str, zCfg)}, acctRelaxActions: []AccountAction{newAccountRelaxAction(str, zCfg)}, - envWarningActions: []EnvironmentAction{newEnvironmentWarningAction(emailCfg, str)}, - envLimitActions: []EnvironmentAction{newEnvironmentLimitAction(str, zCfg)}, - envRelaxActions: []EnvironmentAction{newEnvironmentRelaxAction(str, zCfg)}, - shrWarningActions: []ShareAction{newShareWarningAction(emailCfg, str)}, - shrLimitActions: []ShareAction{newShareLimitAction(str, zCfg)}, - shrRelaxActions: []ShareAction{newShareRelaxAction(str, zCfg)}, close: make(chan struct{}), join: make(chan struct{}), } @@ -314,7 +302,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { } // run account limit actions for _, action := range a.acctLimitActions { - if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil { + if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } } @@ -351,7 +339,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { } // run account warning actions for _, action := range a.acctWarningActions { - if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil { + if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } } @@ -361,168 +349,6 @@ func (a *Agent) enforce(u *metrics.Usage) error { } else { logrus.Debugf("already warned account '#%d' at %v", u.AccountId, warnedAt) } - - } else { - if enforce, warning, rxBytes, txBytes, err := a.checkEnvironmentLimit(u.EnvironmentId); err == nil { - if enforce { - enforced := false - var enforcedAt time.Time - if empty, err := a.str.IsEnvironmentLimitJournalEmpty(int(u.EnvironmentId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestEnvironmentLimitJournal(int(u.EnvironmentId), trx); err == nil { - enforced = latest.Action == store.LimitLimitAction - enforcedAt = latest.UpdatedAt - } - } - - if !enforced { - _, err := a.str.CreateEnvironmentLimitJournal(&store.EnvironmentLimitJournal{ - EnvironmentId: int(u.EnvironmentId), - RxBytes: rxBytes, - TxBytes: txBytes, - Action: store.LimitLimitAction, - }, trx) - if err != nil { - return err - } - env, err := a.str.GetEnvironment(int(u.EnvironmentId), trx) - if err != nil { - return err - } - // run environment limit actions - for _, action := range a.envLimitActions { - if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - if err := trx.Commit(); err != nil { - return err - } - } else { - logrus.Debugf("already enforced limit for environment '#%d' at %v", u.EnvironmentId, enforcedAt) - } - - } else if warning { - warned := false - var warnedAt time.Time - if empty, err := a.str.IsEnvironmentLimitJournalEmpty(int(u.EnvironmentId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestEnvironmentLimitJournal(int(u.EnvironmentId), trx); err == nil { - warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction - warnedAt = latest.UpdatedAt - } - } - - if !warned { - _, err := a.str.CreateEnvironmentLimitJournal(&store.EnvironmentLimitJournal{ - EnvironmentId: int(u.EnvironmentId), - RxBytes: rxBytes, - TxBytes: txBytes, - Action: store.WarningLimitAction, - }, trx) - if err != nil { - return err - } - env, err := a.str.GetEnvironment(int(u.EnvironmentId), trx) - if err != nil { - return err - } - // run environment warning actions - for _, action := range a.envWarningActions { - if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - if err := trx.Commit(); err != nil { - return err - } - } else { - logrus.Debugf("already warned environment '#%d' at %v", u.EnvironmentId, warnedAt) - } - - } else { - if enforce, warning, rxBytes, txBytes, err := a.checkShareLimit(u.ShareToken); err == nil { - if enforce { - shr, err := a.str.FindShareWithToken(u.ShareToken, trx) - if err != nil { - return err - } - - enforced := false - var enforcedAt time.Time - if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty { - if latest, err := a.str.FindLatestShareLimitJournal(shr.Id, trx); err == nil { - enforced = latest.Action == store.LimitLimitAction - enforcedAt = latest.UpdatedAt - } - } - - if !enforced { - _, err := a.str.CreateShareLimitJournal(&store.ShareLimitJournal{ - ShareId: shr.Id, - RxBytes: rxBytes, - TxBytes: txBytes, - Action: store.LimitLimitAction, - }, trx) - if err != nil { - return err - } - // run share limit actions - for _, action := range a.shrLimitActions { - if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - if err := trx.Commit(); err != nil { - return err - } - } else { - logrus.Debugf("already enforced limit for share '%v' at %v", shr.Token, enforcedAt) - } - - } else if warning { - shr, err := a.str.FindShareWithToken(u.ShareToken, trx) - if err != nil { - return err - } - - warned := false - var warnedAt time.Time - if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty { - if latest, err := a.str.FindLatestShareLimitJournal(shr.Id, trx); err == nil { - warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction - warnedAt = latest.UpdatedAt - } - } - - if !warned { - _, err := a.str.CreateShareLimitJournal(&store.ShareLimitJournal{ - ShareId: shr.Id, - RxBytes: rxBytes, - TxBytes: txBytes, - Action: store.WarningLimitAction, - }, trx) - if err != nil { - return err - } - // run share warning actions - for _, action := range a.shrWarningActions { - if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - if err := trx.Commit(); err != nil { - return err - } - } else { - logrus.Debugf("already warned share '%v' at %v", shr.Token, warnedAt) - } - } - } else { - logrus.Error(err) - } - } - } else { - logrus.Error(err) - } } } else { logrus.Error(err) @@ -542,78 +368,6 @@ func (a *Agent) relax() error { commit := false - if sljs, err := a.str.FindAllLatestShareLimitJournal(trx); err == nil { - for _, slj := range sljs { - if shr, err := a.str.GetShare(slj.ShareId, trx); err == nil { - if slj.Action == store.WarningLimitAction || slj.Action == store.LimitLimitAction { - if enforce, warning, rxBytes, txBytes, err := a.checkShareLimit(shr.Token); err == nil { - if !enforce && !warning { - if slj.Action == store.LimitLimitAction { - // run relax actions for share - for _, action := range a.shrRelaxActions { - if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - } else { - logrus.Infof("relaxing warning for '%v'", shr.Token) - } - if err := a.str.DeleteShareLimitJournalForShare(shr.Id, trx); err == nil { - commit = true - } else { - logrus.Errorf("error deleting share_limit_journal for '%v'", shr.Token) - } - } else { - logrus.Infof("share '%v' still over limit", shr.Token) - } - } else { - logrus.Errorf("error checking share limit for '%v': %v", shr.Token, err) - } - } - } else { - logrus.Errorf("error getting share for '#%d': %v", slj.ShareId, err) - } - } - } else { - return err - } - - if eljs, err := a.str.FindAllLatestEnvironmentLimitJournal(trx); err == nil { - for _, elj := range eljs { - if env, err := a.str.GetEnvironment(elj.EnvironmentId, trx); err == nil { - if elj.Action == store.WarningLimitAction || elj.Action == store.LimitLimitAction { - if enforce, warning, rxBytes, txBytes, err := a.checkEnvironmentLimit(int64(elj.EnvironmentId)); err == nil { - if !enforce && !warning { - if elj.Action == store.LimitLimitAction { - // run relax actions for environment - for _, action := range a.envRelaxActions { - if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - } else { - logrus.Infof("relaxing warning for '%v'", env.ZId) - } - if err := a.str.DeleteEnvironmentLimitJournalForEnvironment(env.Id, trx); err == nil { - commit = true - } else { - logrus.Errorf("error deleteing environment_limit_journal for '%v': %v", env.ZId, err) - } - } else { - logrus.Infof("environment '%v' still over limit", env.ZId) - } - } else { - logrus.Errorf("error checking environment limit for '%v': %v", env.ZId, err) - } - } - } else { - logrus.Errorf("error getting environment for '#%d': %v", elj.EnvironmentId, err) - } - } - } else { - return err - } - if aljs, err := a.str.FindAllLatestAccountLimitJournal(trx); err == nil { for _, alj := range aljs { if acct, err := a.str.GetAccount(alj.AccountId, trx); err == nil { @@ -623,7 +377,7 @@ func (a *Agent) relax() error { if alj.Action == store.LimitLimitAction { // run relax actions for account for _, action := range a.acctRelaxActions { - if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil { + if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } } @@ -662,8 +416,8 @@ func (a *Agent) relax() error { func (a *Agent) checkAccountLimit(acctId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) { period := 24 * time.Hour limit := DefaultBandwidthPerPeriod() - if a.cfg.Bandwidth != nil && a.cfg.Bandwidth.PerAccount != nil { - limit = a.cfg.Bandwidth.PerAccount + if a.cfg.Bandwidth != nil && a.cfg.Bandwidth != nil { + limit = a.cfg.Bandwidth } if limit.Period > 0 { period = limit.Period @@ -677,46 +431,6 @@ func (a *Agent) checkAccountLimit(acctId int64) (enforce, warning bool, rxBytes, return enforce, warning, rx, tx, nil } -func (a *Agent) checkEnvironmentLimit(envId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) { - period := 24 * time.Hour - limit := DefaultBandwidthPerPeriod() - if a.cfg.Bandwidth != nil && a.cfg.Bandwidth.PerEnvironment != nil { - limit = a.cfg.Bandwidth.PerEnvironment - } - if limit.Period > 0 { - period = limit.Period - } - rx, tx, err := a.ifx.totalRxTxForEnvironment(envId, period) - if err != nil { - logrus.Error(err) - } - - enforce, warning = a.checkLimit(limit, rx, tx) - return enforce, warning, rx, tx, nil -} - -func (a *Agent) checkShareLimit(shrToken string) (enforce, warning bool, rxBytes, txBytes int64, err error) { - period := 24 * time.Hour - limit := DefaultBandwidthPerPeriod() - if a.cfg.Bandwidth != nil && a.cfg.Bandwidth.PerShare != nil { - limit = a.cfg.Bandwidth.PerShare - } - if limit.Period > 0 { - period = limit.Period - } - rx, tx, err := a.ifx.totalRxTxForShare(shrToken, period) - if err != nil { - logrus.Error(err) - } - - enforce, warning = a.checkLimit(limit, rx, tx) - if enforce || warning { - logrus.Debugf("'%v': %v", shrToken, describeLimit(limit, rx, tx)) - } - - return enforce, warning, rx, tx, nil -} - func (a *Agent) checkLimit(cfg *BandwidthPerPeriod, rx, tx int64) (enforce, warning bool) { if cfg.Limit.Rx != Unlimited && rx > cfg.Limit.Rx { return true, false diff --git a/controller/limits/config.go b/controller/limits/config.go index 412fa35a..bd2c28d2 100644 --- a/controller/limits/config.go +++ b/controller/limits/config.go @@ -9,17 +9,11 @@ type Config struct { Shares int ReservedShares int UniqueNames int - Bandwidth *BandwidthConfig + Bandwidth *BandwidthPerPeriod Cycle time.Duration Enforcing bool } -type BandwidthConfig struct { - PerAccount *BandwidthPerPeriod - PerEnvironment *BandwidthPerPeriod - PerShare *BandwidthPerPeriod -} - type BandwidthPerPeriod struct { Period time.Duration Warning *Bandwidth @@ -54,12 +48,8 @@ func DefaultConfig() *Config { Shares: Unlimited, ReservedShares: Unlimited, UniqueNames: Unlimited, - Bandwidth: &BandwidthConfig{ - PerAccount: DefaultBandwidthPerPeriod(), - PerEnvironment: DefaultBandwidthPerPeriod(), - PerShare: DefaultBandwidthPerPeriod(), - }, - Enforcing: false, - Cycle: 15 * time.Minute, + Bandwidth: DefaultBandwidthPerPeriod(), + Enforcing: false, + Cycle: 15 * time.Minute, } } diff --git a/controller/limits/environmentLimitAction.go b/controller/limits/environmentLimitAction.go deleted file mode 100644 index 31f10cec..00000000 --- a/controller/limits/environmentLimitAction.go +++ /dev/null @@ -1,41 +0,0 @@ -package limits - -import ( - "github.com/jmoiron/sqlx" - "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/controller/zrokEdgeSdk" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -type environmentLimitAction struct { - str *store.Store - zCfg *zrokEdgeSdk.Config -} - -func newEnvironmentLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *environmentLimitAction { - return &environmentLimitAction{str, zCfg} -} - -func (a *environmentLimitAction) HandleEnvironment(env *store.Environment, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { - logrus.Infof("limiting '%v'", env.ZId) - - shrs, err := a.str.FindSharesForEnvironment(env.Id, trx) - if err != nil { - return errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId) - } - - edge, err := zrokEdgeSdk.Client(a.zCfg) - if err != nil { - return err - } - - for _, shr := range shrs { - if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil { - return errors.Wrapf(err, "error deleting dial service policy for '%v'", shr.Token) - } - logrus.Infof("removed dial service policy for share '%v' of environment '%v'", shr.Token, env.ZId) - } - - return nil -} diff --git a/controller/limits/environmentRelaxAction.go b/controller/limits/environmentRelaxAction.go deleted file mode 100644 index aae04185..00000000 --- a/controller/limits/environmentRelaxAction.go +++ /dev/null @@ -1,50 +0,0 @@ -package limits - -import ( - "github.com/jmoiron/sqlx" - "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/controller/zrokEdgeSdk" - "github.com/openziti/zrok/sdk/golang/sdk" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -type environmentRelaxAction struct { - str *store.Store - zCfg *zrokEdgeSdk.Config -} - -func newEnvironmentRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *environmentRelaxAction { - return &environmentRelaxAction{str, zCfg} -} - -func (a *environmentRelaxAction) HandleEnvironment(env *store.Environment, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error { - logrus.Infof("relaxing '%v'", env.ZId) - - shrs, err := a.str.FindSharesForEnvironment(env.Id, trx) - if err != nil { - return errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId) - } - - edge, err := zrokEdgeSdk.Client(a.zCfg) - if err != nil { - return err - } - - for _, shr := range shrs { - if !shr.Deleted { - switch shr.ShareMode { - case string(sdk.PublicShareMode): - if err := relaxPublicShare(a.str, edge, shr, trx); err != nil { - return err - } - case string(sdk.PrivateShareMode): - if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil { - return err - } - } - } - } - - return nil -} diff --git a/controller/limits/environmentWarningAction.go b/controller/limits/environmentWarningAction.go deleted file mode 100644 index 2d5b04a1..00000000 --- a/controller/limits/environmentWarningAction.go +++ /dev/null @@ -1,58 +0,0 @@ -package limits - -import ( - "github.com/jmoiron/sqlx" - "github.com/openziti/zrok/controller/emailUi" - "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -type environmentWarningAction struct { - str *store.Store - cfg *emailUi.Config -} - -func newEnvironmentWarningAction(cfg *emailUi.Config, str *store.Store) *environmentWarningAction { - return &environmentWarningAction{str, cfg} -} - -func (a *environmentWarningAction) HandleEnvironment(env *store.Environment, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error { - logrus.Infof("warning '%v'", env.ZId) - - if a.cfg != nil { - if env.AccountId != nil { - acct, err := a.str.GetAccount(*env.AccountId, trx) - if err != nil { - return err - } - - rxLimit := "unlimited bytes" - if limit.Limit.Rx != Unlimited { - rxLimit = util.BytesToSize(limit.Limit.Rx) - } - txLimit := "unlimited bytes" - if limit.Limit.Tx != Unlimited { - txLimit = util.BytesToSize(limit.Limit.Tx) - } - totalLimit := "unlimited bytes" - if limit.Limit.Total != Unlimited { - totalLimit = util.BytesToSize(limit.Limit.Total) - } - - detail := newDetailMessage() - detail = detail.append("Your environment '%v' has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", env.Description, util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes)) - detail = detail.append("This zrok instance only allows a share to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, limit.Period) - detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit).", limit.Period) - - if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil { - return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email) - } - } - } else { - logrus.Warnf("skipping warning email for environment limit; no email configuration specified") - } - - return nil -} diff --git a/controller/limits/shareLimitAction.go b/controller/limits/shareLimitAction.go deleted file mode 100644 index 8a4fc8b9..00000000 --- a/controller/limits/shareLimitAction.go +++ /dev/null @@ -1,38 +0,0 @@ -package limits - -import ( - "github.com/jmoiron/sqlx" - "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/controller/zrokEdgeSdk" - "github.com/sirupsen/logrus" -) - -type shareLimitAction struct { - str *store.Store - zCfg *zrokEdgeSdk.Config -} - -func newShareLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *shareLimitAction { - return &shareLimitAction{str, zCfg} -} - -func (a *shareLimitAction) HandleShare(shr *store.Share, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { - logrus.Infof("limiting '%v'", shr.Token) - - env, err := a.str.GetEnvironment(shr.EnvironmentId, trx) - if err != nil { - return err - } - - edge, err := zrokEdgeSdk.Client(a.zCfg) - if err != nil { - return err - } - - if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil { - return err - } - logrus.Infof("removed dial service policy for '%v'", shr.Token) - - return nil -} diff --git a/controller/limits/shareRelaxAction.go b/controller/limits/shareRelaxAction.go deleted file mode 100644 index 5f05496d..00000000 --- a/controller/limits/shareRelaxAction.go +++ /dev/null @@ -1,89 +0,0 @@ -package limits - -import ( - "github.com/jmoiron/sqlx" - "github.com/openziti/edge-api/rest_management_api_client" - "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/controller/zrokEdgeSdk" - "github.com/openziti/zrok/sdk/golang/sdk" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -type shareRelaxAction struct { - str *store.Store - zCfg *zrokEdgeSdk.Config -} - -func newShareRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *shareRelaxAction { - return &shareRelaxAction{str, zCfg} -} - -func (a *shareRelaxAction) HandleShare(shr *store.Share, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { - logrus.Infof("relaxing '%v'", shr.Token) - - if !shr.Deleted { - edge, err := zrokEdgeSdk.Client(a.zCfg) - if err != nil { - return err - } - - switch shr.ShareMode { - case string(sdk.PublicShareMode): - if err := relaxPublicShare(a.str, edge, shr, trx); err != nil { - return err - } - case string(sdk.PrivateShareMode): - if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil { - return err - } - } - } - - return nil -} - -func relaxPublicShare(str *store.Store, edge *rest_management_api_client.ZitiEdgeManagement, shr *store.Share, trx *sqlx.Tx) error { - env, err := str.GetEnvironment(shr.EnvironmentId, trx) - if err != nil { - return errors.Wrap(err, "error finding environment") - } - - fe, err := str.FindFrontendPubliclyNamed(*shr.FrontendSelection, trx) - if err != nil { - return errors.Wrapf(err, "error finding frontend name '%v' for '%v'", *shr.FrontendSelection, shr.Token) - } - - if err := zrokEdgeSdk.CreateServicePolicyDial(env.ZId+"-"+shr.ZId+"-dial", shr.ZId, []string{fe.ZId}, zrokEdgeSdk.ZrokShareTags(shr.Token).SubTags, edge); err != nil { - return errors.Wrapf(err, "error creating dial service policy for '%v'", shr.Token) - } - logrus.Infof("added dial service policy for '%v'", shr.Token) - return nil -} - -func relaxPrivateShare(str *store.Store, edge *rest_management_api_client.ZitiEdgeManagement, shr *store.Share, trx *sqlx.Tx) error { - fes, err := str.FindFrontendsForPrivateShare(shr.Id, trx) - if err != nil { - return errors.Wrapf(err, "error finding frontends for share '%v'", shr.Token) - } - for _, fe := range fes { - if fe.EnvironmentId != nil { - env, err := str.GetEnvironment(*fe.EnvironmentId, trx) - if err != nil { - return errors.Wrapf(err, "error getting environment for frontend '%v'", fe.Token) - } - - addlTags := map[string]interface{}{ - "zrokEnvironmentZId": env.ZId, - "zrokFrontendToken": fe.Token, - "zrokShareToken": shr.Token, - } - if err := zrokEdgeSdk.CreateServicePolicyDial(fe.Token+"-"+env.ZId+"-"+shr.ZId+"-dial", shr.ZId, []string{env.ZId}, addlTags, edge); err != nil { - return errors.Wrapf(err, "unable to create dial policy for frontend '%v'", fe.Token) - } - - logrus.Infof("added dial service policy for share '%v' to private frontend '%v'", shr.Token, fe.Token) - } - } - return nil -} diff --git a/controller/limits/shareWarningAction.go b/controller/limits/shareWarningAction.go deleted file mode 100644 index 764b6d3f..00000000 --- a/controller/limits/shareWarningAction.go +++ /dev/null @@ -1,63 +0,0 @@ -package limits - -import ( - "github.com/jmoiron/sqlx" - "github.com/openziti/zrok/controller/emailUi" - "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -type shareWarningAction struct { - str *store.Store - cfg *emailUi.Config -} - -func newShareWarningAction(cfg *emailUi.Config, str *store.Store) *shareWarningAction { - return &shareWarningAction{str, cfg} -} - -func (a *shareWarningAction) HandleShare(shr *store.Share, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error { - logrus.Infof("warning '%v'", shr.Token) - - if a.cfg != nil { - env, err := a.str.GetEnvironment(shr.EnvironmentId, trx) - if err != nil { - return err - } - - if env.AccountId != nil { - acct, err := a.str.GetAccount(*env.AccountId, trx) - if err != nil { - return err - } - - rxLimit := "unlimited bytes" - if limit.Limit.Rx != Unlimited { - rxLimit = util.BytesToSize(limit.Limit.Rx) - } - txLimit := "unlimited bytes" - if limit.Limit.Tx != Unlimited { - txLimit = util.BytesToSize(limit.Limit.Tx) - } - totalLimit := "unlimited bytes" - if limit.Limit.Total != Unlimited { - totalLimit = util.BytesToSize(limit.Limit.Total) - } - - detail := newDetailMessage() - detail = detail.append("Your share '%v' has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", shr.Token, util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes)) - detail = detail.append("This zrok instance only allows a share to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, limit.Period) - detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit).", limit.Period) - - if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil { - return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email) - } - } - } else { - logrus.Warnf("skipping warning email for share limit; no email configuration specified") - } - - return nil -} From af5197d04196bee8d2096e797fad6ac4d4ef3294 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 30 May 2024 13:49:45 -0400 Subject: [PATCH 020/117] update controller configuration limits example reflecting new structure (#606) --- etc/ctrl.yml | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/etc/ctrl.yml b/etc/ctrl.yml index f1abbe94..4ecd5392 100644 --- a/etc/ctrl.yml +++ b/etc/ctrl.yml @@ -84,36 +84,15 @@ limits: reserved_shares: -1 unique_names: -1 bandwidth: - per_account: - period: 5m - warning: - rx: -1 - tx: -1 - total: 7242880 - limit: - rx: -1 - tx: -1 - total: 10485760 - per_environment: - period: 5m - warning: - rx: -1 - tx: -1 - total: -1 - limit: - rx: -1 - tx: -1 - total: -1 - per_share: - period: 5m - warning: - rx: -1 - tx: -1 - total: -1 - limit: - rx: -1 - tx: -1 - total: -1 + period: 5m + warning: + rx: -1 + tx: -1 + total: 7242880 + limit: + rx: -1 + tx: -1 + total: 10485760 enforcing: false cycle: 5m From ea5670b4aefd92c9d27f818b3c824e20c184e2b4 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 30 May 2024 14:27:39 -0400 Subject: [PATCH 021/117] new bandwidth_limit_journal table (#606) --- controller/limits/agent.go | 32 +------- controller/store/accountLimitJournal_test.go | 79 ------------------- .../025_v0_4_31_bandwidth_limit_journal.sql | 19 +++++ .../025_v0_4_31_bandwidth_limit_journal.sql | 16 ++++ 4 files changed, 37 insertions(+), 109 deletions(-) delete mode 100644 controller/store/accountLimitJournal_test.go create mode 100644 controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql create mode 100644 controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 6fddc103..123da3bb 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -1,14 +1,12 @@ package limits import ( - "fmt" "github.com/jmoiron/sqlx" "github.com/openziti/zrok/controller/emailUi" "github.com/openziti/zrok/controller/metrics" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/controller/zrokEdgeSdk" "github.com/openziti/zrok/sdk/golang/sdk" - "github.com/openziti/zrok/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "reflect" @@ -83,7 +81,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { return true, nil } -func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, shareMode sdk.ShareMode, backendMode sdk.BackendMode, trx *sqlx.Tx) (bool, error) { +func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ sdk.ShareMode, _ sdk.BackendMode, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err @@ -416,7 +414,7 @@ func (a *Agent) relax() error { func (a *Agent) checkAccountLimit(acctId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) { period := 24 * time.Hour limit := DefaultBandwidthPerPeriod() - if a.cfg.Bandwidth != nil && a.cfg.Bandwidth != nil { + if a.cfg.Bandwidth != nil { limit = a.cfg.Bandwidth } if limit.Period > 0 { @@ -454,29 +452,3 @@ func (a *Agent) checkLimit(cfg *BandwidthPerPeriod, rx, tx int64) (enforce, warn return false, false } - -func describeLimit(cfg *BandwidthPerPeriod, rx, tx int64) string { - out := "" - - if cfg.Limit.Rx != Unlimited && rx > cfg.Limit.Rx { - out += fmt.Sprintf("['%v' over rx limit '%v']", util.BytesToSize(rx), util.BytesToSize(cfg.Limit.Rx)) - } - if cfg.Limit.Tx != Unlimited && tx > cfg.Limit.Tx { - out += fmt.Sprintf("['%v' over tx limit '%v']", util.BytesToSize(tx), util.BytesToSize(cfg.Limit.Tx)) - } - if cfg.Limit.Total != Unlimited && rx+tx > cfg.Limit.Total { - out += fmt.Sprintf("['%v' over total limit '%v']", util.BytesToSize(rx+tx), util.BytesToSize(cfg.Limit.Total)) - } - - if cfg.Warning.Rx != Unlimited && rx > cfg.Warning.Rx { - out += fmt.Sprintf("['%v' over rx warning '%v']", util.BytesToSize(rx), util.BytesToSize(cfg.Warning.Rx)) - } - if cfg.Warning.Tx != Unlimited && tx > cfg.Warning.Tx { - out += fmt.Sprintf("['%v' over tx warning '%v']", util.BytesToSize(tx), util.BytesToSize(cfg.Warning.Tx)) - } - if cfg.Warning.Total != Unlimited && rx+tx > cfg.Warning.Total { - out += fmt.Sprintf("['%v' over total warning '%v']", util.BytesToSize(rx+tx), util.BytesToSize(cfg.Warning.Total)) - } - - return out -} diff --git a/controller/store/accountLimitJournal_test.go b/controller/store/accountLimitJournal_test.go deleted file mode 100644 index add540e2..00000000 --- a/controller/store/accountLimitJournal_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package store - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestAccountLimitJournal(t *testing.T) { - str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"}) - assert.Nil(t, err) - assert.NotNil(t, str) - - trx, err := str.Begin() - assert.Nil(t, err) - assert.NotNil(t, trx) - - aljEmpty, err := str.IsAccountLimitJournalEmpty(1, trx) - assert.Nil(t, err) - assert.True(t, aljEmpty) - - acctId, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt", Password: "password", Token: "token", Limitless: false, Deleted: false}, trx) - assert.Nil(t, err) - - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 1024, TxBytes: 2048, Action: WarningLimitAction}, trx) - assert.Nil(t, err) - - aljEmpty, err = str.IsAccountLimitJournalEmpty(acctId, trx) - assert.Nil(t, err) - assert.False(t, aljEmpty) - - latestAlj, err := str.FindLatestAccountLimitJournal(acctId, trx) - assert.Nil(t, err) - assert.NotNil(t, latestAlj) - assert.Equal(t, int64(1024), latestAlj.RxBytes) - assert.Equal(t, int64(2048), latestAlj.TxBytes) - assert.Equal(t, WarningLimitAction, latestAlj.Action) - - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 2048, TxBytes: 4096, Action: LimitLimitAction}, trx) - assert.Nil(t, err) - - latestAlj, err = str.FindLatestAccountLimitJournal(acctId, trx) - assert.Nil(t, err) - assert.NotNil(t, latestAlj) - assert.Equal(t, int64(2048), latestAlj.RxBytes) - assert.Equal(t, int64(4096), latestAlj.TxBytes) - assert.Equal(t, LimitLimitAction, latestAlj.Action) -} - -func TestFindAllLatestAccountLimitJournal(t *testing.T) { - str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"}) - assert.Nil(t, err) - assert.NotNil(t, str) - - trx, err := str.Begin() - assert.Nil(t, err) - assert.NotNil(t, trx) - - acctId1, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt1", Password: "password1", Token: "token1", Limitless: false, Deleted: false}, trx) - assert.Nil(t, err) - - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: WarningLimitAction}, trx) - assert.Nil(t, err) - _, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: ClearLimitAction}, trx) - assert.Nil(t, err) - aljId13, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: LimitLimitAction}, trx) - assert.Nil(t, err) - - acctId2, err := str.CreateAccount(&Account{Email: "someone@somewhere.com", Salt: "salt2", Password: "password2", Token: "token2", Limitless: false, Deleted: false}, trx) - assert.Nil(t, err) - - aljId21, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId2, RxBytes: 2048, TxBytes: 4096, Action: WarningLimitAction}, trx) - assert.Nil(t, err) - - aljs, err := str.FindAllLatestAccountLimitJournal(trx) - assert.Nil(t, err) - assert.Equal(t, 2, len(aljs)) - assert.Equal(t, aljId13, aljs[0].Id) - assert.Equal(t, aljId21, aljs[1].Id) -} diff --git a/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql b/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql new file mode 100644 index 00000000..c234d9a4 --- /dev/null +++ b/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql @@ -0,0 +1,19 @@ +-- +migrate Up + +drop table account_limit_journal; +drop table environment_limit_journal; +drop table share_limit_journal; + +drop type limit_action_type; +create type limit_action_type as enum ('warning', 'limit'); + +create table bandwidth_limit_journal ( + id serial primary key, + account_id integer references accounts (id) not null, + limit_class integer references limit_classes, + action limit_action_type not null, + rx_bytes bigint not null, + tx_bytes bigint not null, + created_at timestamptz not null default(current_timestamp), + updated_at timestamptz not null default(current_timestamp) +); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql b/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql new file mode 100644 index 00000000..6d9bd0fe --- /dev/null +++ b/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql @@ -0,0 +1,16 @@ +-- +migrate Up + +drop table account_limit_journal; +drop table environment_limit_journal; +drop table share_limit_journal; + +create table bandwidth_limit_journal ( + id serial primary key, + account_id integer references accounts (id) not null, + limit_class integer references limit_classes, + action string not null, + rx_bytes bigint not null, + tx_bytes bigint not null, + created_at timestamptz not null default(current_timestamp), + updated_at timestamptz not null default(current_timestamp) +); \ No newline at end of file From 481cc7f7ad7d6d8d4fe8d294f7517b0d7d41b9ed Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 31 May 2024 14:26:29 -0400 Subject: [PATCH 022/117] replacing the old account/environment/share limit journals with the new bandwidth limit journal (#606) --- controller/limits/agent.go | 74 +++------------ controller/overview.go | 76 +-------------- controller/store/accountLimitJournal.go | 65 ------------- controller/store/bandwidthLimitJournal.go | 66 +++++++++++++ controller/store/environmentLimitJournal.go | 93 ------------------- controller/store/model.go | 1 - controller/store/shareLimitJournal.go | 93 ------------------- .../025_v0_4_31_bandwidth_limit_journal.sql | 6 +- 8 files changed, 89 insertions(+), 385 deletions(-) delete mode 100644 controller/store/accountLimitJournal.go create mode 100644 controller/store/bandwidthLimitJournal.go delete mode 100644 controller/store/environmentLimitJournal.go delete mode 100644 controller/store/shareLimitJournal.go diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 123da3bb..d63a45c8 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -56,8 +56,8 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err } - if empty, err := a.str.IsAccountLimitJournalEmpty(acctId, trx); err == nil && !empty { - alj, err := a.str.FindLatestAccountLimitJournal(acctId, trx) + if empty, err := a.str.IsBandwidthLimitJournalEmpty(acctId, trx); err == nil && !empty { + alj, err := a.str.FindLatestBandwidthLimitJournal(acctId, trx) if err != nil { return false, err } @@ -86,8 +86,8 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err } - if empty, err := a.str.IsAccountLimitJournalEmpty(acctId, trx); err == nil && !empty { - alj, err := a.str.FindLatestAccountLimitJournal(acctId, trx) + if empty, err := a.str.IsBandwidthLimitJournalEmpty(acctId, trx); err == nil && !empty { + alj, err := a.str.FindLatestBandwidthLimitJournal(acctId, trx) if err != nil { return false, err } @@ -98,18 +98,6 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s return false, err } - if empty, err := a.str.IsEnvironmentLimitJournalEmpty(envId, trx); err == nil && !empty { - elj, err := a.str.FindLatestEnvironmentLimitJournal(envId, trx) - if err != nil { - return false, err - } - if elj.Action == store.LimitLimitAction { - return false, nil - } - } else if err != nil { - return false, err - } - alc, err := a.str.FindLimitClassesForAccount(acctId, trx) if err != nil { logrus.Errorf("error finding limit classes for account with id '%d': %v", acctId, err) @@ -167,8 +155,8 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty { - slj, err := a.str.FindLatestShareLimitJournal(shr.Id, trx) + if empty, err := a.str.IsBandwidthLimitJournalEmpty(shr.Id, trx); err == nil && !empty { + slj, err := a.str.FindLatestBandwidthLimitJournal(shr.Id, trx) if err != nil { return false, err } @@ -178,40 +166,6 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { } else if err != nil { return false, err } - - env, err := a.str.GetEnvironment(shr.EnvironmentId, trx) - if err != nil { - return false, err - } - if empty, err := a.str.IsEnvironmentLimitJournalEmpty(env.Id, trx); err == nil && !empty { - elj, err := a.str.FindLatestEnvironmentLimitJournal(env.Id, trx) - if err != nil { - return false, err - } - if elj.Action == store.LimitLimitAction { - return false, nil - } - } else if err != nil { - return false, err - } - - if env.AccountId != nil { - acct, err := a.str.GetAccount(*env.AccountId, trx) - if err != nil { - return false, err - } - if empty, err := a.str.IsAccountLimitJournalEmpty(acct.Id, trx); err == nil && !empty { - alj, err := a.str.FindLatestAccountLimitJournal(acct.Id, trx) - if err != nil { - return false, err - } - if alj.Action == store.LimitLimitAction { - return false, nil - } - } else if err != nil { - return false, err - } - } } return true, nil } @@ -277,15 +231,15 @@ func (a *Agent) enforce(u *metrics.Usage) error { if enforce { enforced := false var enforcedAt time.Time - if empty, err := a.str.IsAccountLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestAccountLimitJournal(int(u.AccountId), trx); err == nil { + if empty, err := a.str.IsBandwidthLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { + if latest, err := a.str.FindLatestBandwidthLimitJournal(int(u.AccountId), trx); err == nil { enforced = latest.Action == store.LimitLimitAction enforcedAt = latest.UpdatedAt } } if !enforced { - _, err := a.str.CreateAccountLimitJournal(&store.AccountLimitJournal{ + _, err := a.str.CreateBandwidthLimitJournalEntry(&store.BandwidthLimitJournalEntry{ AccountId: int(u.AccountId), RxBytes: rxBytes, TxBytes: txBytes, @@ -314,15 +268,15 @@ func (a *Agent) enforce(u *metrics.Usage) error { } else if warning { warned := false var warnedAt time.Time - if empty, err := a.str.IsAccountLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestAccountLimitJournal(int(u.AccountId), trx); err == nil { + if empty, err := a.str.IsBandwidthLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { + if latest, err := a.str.FindLatestBandwidthLimitJournal(int(u.AccountId), trx); err == nil { warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction warnedAt = latest.UpdatedAt } } if !warned { - _, err := a.str.CreateAccountLimitJournal(&store.AccountLimitJournal{ + _, err := a.str.CreateBandwidthLimitJournalEntry(&store.BandwidthLimitJournalEntry{ AccountId: int(u.AccountId), RxBytes: rxBytes, TxBytes: txBytes, @@ -366,7 +320,7 @@ func (a *Agent) relax() error { commit := false - if aljs, err := a.str.FindAllLatestAccountLimitJournal(trx); err == nil { + if aljs, err := a.str.FindAllLatestBandwidthLimitJournal(trx); err == nil { for _, alj := range aljs { if acct, err := a.str.GetAccount(alj.AccountId, trx); err == nil { if alj.Action == store.WarningLimitAction || alj.Action == store.LimitLimitAction { @@ -382,7 +336,7 @@ func (a *Agent) relax() error { } else { logrus.Infof("relaxing warning for '%v'", acct.Email) } - if err := a.str.DeleteAccountLimitJournalForAccount(acct.Id, trx); err == nil { + if err := a.str.DeleteBandwidthLimitJournal(acct.Id, trx); err == nil { commit = true } else { logrus.Errorf("error deleting account_limit_journal for '%v': %v", acct.Email, err) diff --git a/controller/overview.go b/controller/overview.go index 5291a6c8..f36c31f8 100644 --- a/controller/overview.go +++ b/controller/overview.go @@ -27,11 +27,6 @@ func (h *overviewHandler) Handle(_ metadata.OverviewParams, principal *rest_mode logrus.Errorf("error finding environments for '%v': %v", principal.Email, err) return metadata.NewOverviewInternalServerError() } - elm, err := newEnvironmentsLimitedMap(envs, trx) - if err != nil { - logrus.Errorf("error finding limited environments for '%v': %v", principal.Email, err) - return metadata.NewOverviewInternalServerError() - } accountLimited, err := h.isAccountLimited(principal, trx) if err != nil { logrus.Errorf("error checking account limited for '%v': %v", principal.Email, err) @@ -44,7 +39,6 @@ func (h *overviewHandler) Handle(_ metadata.OverviewParams, principal *rest_mode Description: env.Description, Host: env.Host, ZID: env.ZId, - Limited: elm.isLimited(env), CreatedAt: env.CreatedAt.UnixMilli(), UpdatedAt: env.UpdatedAt.UnixMilli(), }, @@ -54,11 +48,6 @@ func (h *overviewHandler) Handle(_ metadata.OverviewParams, principal *rest_mode logrus.Errorf("error finding shares for environment '%v': %v", env.ZId, err) return metadata.NewOverviewInternalServerError() } - slm, err := newSharesLimitedMap(shrs, trx) - if err != nil { - logrus.Errorf("error finding limited shares for environment '%v': %v", env.ZId, err) - return metadata.NewOverviewInternalServerError() - } for _, shr := range shrs { feEndpoint := "" if shr.FrontendEndpoint != nil { @@ -81,7 +70,6 @@ func (h *overviewHandler) Handle(_ metadata.OverviewParams, principal *rest_mode FrontendEndpoint: feEndpoint, BackendProxyEndpoint: beProxyEndpoint, Reserved: shr.Reserved, - Limited: slm.isLimited(shr), CreatedAt: shr.CreatedAt.UnixMilli(), UpdatedAt: shr.UpdatedAt.UnixMilli(), } @@ -116,70 +104,16 @@ func (h *overviewHandler) Handle(_ metadata.OverviewParams, principal *rest_mode } func (h *overviewHandler) isAccountLimited(principal *rest_model_zrok.Principal, trx *sqlx.Tx) (bool, error) { - var alj *store.AccountLimitJournal - aljEmpty, err := str.IsAccountLimitJournalEmpty(int(principal.ID), trx) + var je *store.BandwidthLimitJournalEntry + jEmpty, err := str.IsBandwidthLimitJournalEmpty(int(principal.ID), trx) if err != nil { return false, err } - if !aljEmpty { - alj, err = str.FindLatestAccountLimitJournal(int(principal.ID), trx) + if !jEmpty { + je, err = str.FindLatestBandwidthLimitJournal(int(principal.ID), trx) if err != nil { return false, err } } - return alj != nil && alj.Action == store.LimitLimitAction, nil -} - -type sharesLimitedMap struct { - v map[int]struct{} -} - -func newSharesLimitedMap(shrs []*store.Share, trx *sqlx.Tx) (*sharesLimitedMap, error) { - var shrIds []int - for i := range shrs { - shrIds = append(shrIds, shrs[i].Id) - } - shrsLimited, err := str.FindSelectedLatestShareLimitjournal(shrIds, trx) - if err != nil { - return nil, err - } - slm := &sharesLimitedMap{v: make(map[int]struct{})} - for i := range shrsLimited { - if shrsLimited[i].Action == store.LimitLimitAction { - slm.v[shrsLimited[i].ShareId] = struct{}{} - } - } - return slm, nil -} - -func (m *sharesLimitedMap) isLimited(shr *store.Share) bool { - _, limited := m.v[shr.Id] - return limited -} - -type environmentsLimitedMap struct { - v map[int]struct{} -} - -func newEnvironmentsLimitedMap(envs []*store.Environment, trx *sqlx.Tx) (*environmentsLimitedMap, error) { - var envIds []int - for i := range envs { - envIds = append(envIds, envs[i].Id) - } - envsLimited, err := str.FindSelectedLatestEnvironmentLimitJournal(envIds, trx) - if err != nil { - return nil, err - } - elm := &environmentsLimitedMap{v: make(map[int]struct{})} - for i := range envsLimited { - if envsLimited[i].Action == store.LimitLimitAction { - elm.v[envsLimited[i].EnvironmentId] = struct{}{} - } - } - return elm, nil -} - -func (m *environmentsLimitedMap) isLimited(env *store.Environment) bool { - _, limited := m.v[env.Id] - return limited + return je != nil && je.Action == store.LimitLimitAction, nil } diff --git a/controller/store/accountLimitJournal.go b/controller/store/accountLimitJournal.go deleted file mode 100644 index 2ac6ef83..00000000 --- a/controller/store/accountLimitJournal.go +++ /dev/null @@ -1,65 +0,0 @@ -package store - -import ( - "github.com/jmoiron/sqlx" - "github.com/pkg/errors" -) - -type AccountLimitJournal struct { - Model - AccountId int - RxBytes int64 - TxBytes int64 - Action LimitAction -} - -func (str *Store) CreateAccountLimitJournal(j *AccountLimitJournal, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into account_limit_journal (account_id, rx_bytes, tx_bytes, action) values ($1, $2, $3, $4) returning id") - if err != nil { - return 0, errors.Wrap(err, "error preparing account_limit_journal insert statement") - } - var id int - if err := stmt.QueryRow(j.AccountId, j.RxBytes, j.TxBytes, j.Action).Scan(&id); err != nil { - return 0, errors.Wrap(err, "error executing account_limit_journal insert statement") - } - return id, nil -} - -func (str *Store) IsAccountLimitJournalEmpty(acctId int, trx *sqlx.Tx) (bool, error) { - count := 0 - if err := trx.QueryRowx("select count(0) from account_limit_journal where account_id = $1", acctId).Scan(&count); err != nil { - return false, err - } - return count == 0, nil -} - -func (str *Store) FindLatestAccountLimitJournal(acctId int, trx *sqlx.Tx) (*AccountLimitJournal, error) { - j := &AccountLimitJournal{} - if err := trx.QueryRowx("select * from account_limit_journal where account_id = $1 order by id desc limit 1", acctId).StructScan(j); err != nil { - return nil, errors.Wrap(err, "error finding account_limit_journal by account_id") - } - return j, nil -} - -func (str *Store) FindAllLatestAccountLimitJournal(trx *sqlx.Tx) ([]*AccountLimitJournal, error) { - rows, err := trx.Queryx("select id, account_id, rx_bytes, tx_bytes, action, created_at, updated_at from account_limit_journal where id in (select max(id) as id from account_limit_journal group by account_id)") - if err != nil { - return nil, errors.Wrap(err, "error selecting all latest account_limit_journal") - } - var aljs []*AccountLimitJournal - for rows.Next() { - alj := &AccountLimitJournal{} - if err := rows.StructScan(alj); err != nil { - return nil, errors.Wrap(err, "error scanning account_limit_journal") - } - aljs = append(aljs, alj) - } - return aljs, nil -} - -func (str *Store) DeleteAccountLimitJournalForAccount(acctId int, trx *sqlx.Tx) error { - if _, err := trx.Exec("delete from account_limit_journal where account_id = $1", acctId); err != nil { - return errors.Wrapf(err, "error deleting account_limit journal for '#%d'", acctId) - } - return nil -} diff --git a/controller/store/bandwidthLimitJournal.go b/controller/store/bandwidthLimitJournal.go new file mode 100644 index 00000000..eaeeb6eb --- /dev/null +++ b/controller/store/bandwidthLimitJournal.go @@ -0,0 +1,66 @@ +package store + +import ( + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +type BandwidthLimitJournalEntry struct { + Model + AccountId int + LimitClassId *int + Action LimitAction + RxBytes int64 + TxBytes int64 +} + +func (str *Store) CreateBandwidthLimitJournalEntry(j *BandwidthLimitJournalEntry, trx *sqlx.Tx) (int, error) { + stmt, err := trx.Prepare("insert into bandwidth_limit_journal (account_id, limit_class_id, action, rx_bytes, tx_bytes) values ($1, $2, $3, $4, $5) returning id") + if err != nil { + return 0, errors.Wrap(err, "error preparing bandwidth_limit_journal insert statement") + } + var id int + if err := stmt.QueryRow(j.AccountId, j.LimitClassId, j.Action, j.RxBytes, j.TxBytes).Scan(&id); err != nil { + return 0, errors.Wrap(err, "error executing bandwidth_limit_journal insert statement") + } + return id, nil +} + +func (str *Store) IsBandwidthLimitJournalEmpty(acctId int, trx *sqlx.Tx) (bool, error) { + count := 0 + if err := trx.QueryRowx("select count(0) from bandwidth_limit_journal where account_id = $1", acctId).Scan(&count); err != nil { + return false, err + } + return count == 0, nil +} + +func (str *Store) FindLatestBandwidthLimitJournal(acctId int, trx *sqlx.Tx) (*BandwidthLimitJournalEntry, error) { + j := &BandwidthLimitJournalEntry{} + if err := trx.QueryRowx("select * from bandwidth_limit_journal where account_id = $1 order by id desc limit 1", acctId).StructScan(j); err != nil { + return nil, errors.Wrap(err, "error finding bandwidth_limit_journal by account_id") + } + return j, nil +} + +func (str *Store) FindAllLatestBandwidthLimitJournal(trx *sqlx.Tx) ([]*BandwidthLimitJournalEntry, error) { + rows, err := trx.Queryx("select id, account_id, limit_class_id, action, rx_bytes, tx_bytes, created_at, updated_at from bandwidth_limit_journal where id in (select max(id) as id from bandwidth_limit_journal group by account_id)") + if err != nil { + return nil, errors.Wrap(err, "error finding all latest bandwidth_limit_journal") + } + var jes []*BandwidthLimitJournalEntry + for rows.Next() { + je := &BandwidthLimitJournalEntry{} + if err := rows.StructScan(je); err != nil { + return nil, errors.Wrap(err, "error scanning bandwidth_limit_journal") + } + jes = append(jes, je) + } + return jes, nil +} + +func (str *Store) DeleteBandwidthLimitJournal(acctId int, trx *sqlx.Tx) error { + if _, err := trx.Exec("delete from bandwidth_limit_journal where account_id = $1", acctId); err != nil { + return errors.Wrapf(err, "error deleting from bandwidth_limit_journal for account_id = %d", acctId) + } + return nil +} diff --git a/controller/store/environmentLimitJournal.go b/controller/store/environmentLimitJournal.go deleted file mode 100644 index 71d7c040..00000000 --- a/controller/store/environmentLimitJournal.go +++ /dev/null @@ -1,93 +0,0 @@ -package store - -import ( - "fmt" - "github.com/jmoiron/sqlx" - "github.com/pkg/errors" -) - -type EnvironmentLimitJournal struct { - Model - EnvironmentId int - RxBytes int64 - TxBytes int64 - Action LimitAction -} - -func (str *Store) CreateEnvironmentLimitJournal(j *EnvironmentLimitJournal, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into environment_limit_journal (environment_id, rx_bytes, tx_bytes, action) values ($1, $2, $3, $4) returning id") - if err != nil { - return 0, errors.Wrap(err, "error preparing environment_limit_journal insert statement") - } - var id int - if err := stmt.QueryRow(j.EnvironmentId, j.RxBytes, j.TxBytes, j.Action).Scan(&id); err != nil { - return 0, errors.Wrap(err, "error executing environment_limit_journal insert statement") - } - return id, nil -} - -func (str *Store) IsEnvironmentLimitJournalEmpty(envId int, trx *sqlx.Tx) (bool, error) { - count := 0 - if err := trx.QueryRowx("select count(0) from environment_limit_journal where environment_id = $1", envId).Scan(&count); err != nil { - return false, err - } - return count == 0, nil -} - -func (str *Store) FindLatestEnvironmentLimitJournal(envId int, trx *sqlx.Tx) (*EnvironmentLimitJournal, error) { - j := &EnvironmentLimitJournal{} - if err := trx.QueryRowx("select * from environment_limit_journal where environment_id = $1 order by created_at desc limit 1", envId).StructScan(j); err != nil { - return nil, errors.Wrap(err, "error finding environment_limit_journal by environment_id") - } - return j, nil -} - -func (str *Store) FindSelectedLatestEnvironmentLimitJournal(envIds []int, trx *sqlx.Tx) ([]*EnvironmentLimitJournal, error) { - if len(envIds) < 1 { - return nil, nil - } - in := "(" - for i := range envIds { - if i > 0 { - in += ", " - } - in += fmt.Sprintf("%d", envIds[i]) - } - in += ")" - rows, err := trx.Queryx("select id, environment_id, rx_bytes, tx_bytes, action, created_at, updated_at from environment_limit_journal where id in (select max(id) as id from environment_limit_journal group by environment_id) and environment_id in " + in) - if err != nil { - return nil, errors.Wrap(err, "error selecting all latest environment_limit_journal") - } - var eljs []*EnvironmentLimitJournal - for rows.Next() { - elj := &EnvironmentLimitJournal{} - if err := rows.StructScan(elj); err != nil { - return nil, errors.Wrap(err, "error scanning environment_limit_journal") - } - eljs = append(eljs, elj) - } - return eljs, nil -} - -func (str *Store) FindAllLatestEnvironmentLimitJournal(trx *sqlx.Tx) ([]*EnvironmentLimitJournal, error) { - rows, err := trx.Queryx("select id, environment_id, rx_bytes, tx_bytes, action, created_at, updated_at from environment_limit_journal where id in (select max(id) as id from environment_limit_journal group by environment_id)") - if err != nil { - return nil, errors.Wrap(err, "error selecting all latest environment_limit_journal") - } - var eljs []*EnvironmentLimitJournal - for rows.Next() { - elj := &EnvironmentLimitJournal{} - if err := rows.StructScan(elj); err != nil { - return nil, errors.Wrap(err, "error scanning environment_limit_journal") - } - eljs = append(eljs, elj) - } - return eljs, nil -} - -func (str *Store) DeleteEnvironmentLimitJournalForEnvironment(envId int, trx *sqlx.Tx) error { - if _, err := trx.Exec("delete from environment_limit_journal where environment_id = $1", envId); err != nil { - return errors.Wrapf(err, "error deleteing environment_limit_journal for '#%d'", envId) - } - return nil -} diff --git a/controller/store/model.go b/controller/store/model.go index e2275123..e4e0d6ab 100644 --- a/controller/store/model.go +++ b/controller/store/model.go @@ -5,7 +5,6 @@ type LimitAction string const ( LimitLimitAction LimitAction = "limit" WarningLimitAction LimitAction = "warning" - ClearLimitAction LimitAction = "clear" ) type LimitScope string diff --git a/controller/store/shareLimitJournal.go b/controller/store/shareLimitJournal.go deleted file mode 100644 index c0920694..00000000 --- a/controller/store/shareLimitJournal.go +++ /dev/null @@ -1,93 +0,0 @@ -package store - -import ( - "fmt" - "github.com/jmoiron/sqlx" - "github.com/pkg/errors" -) - -type ShareLimitJournal struct { - Model - ShareId int - RxBytes int64 - TxBytes int64 - Action LimitAction -} - -func (str *Store) CreateShareLimitJournal(j *ShareLimitJournal, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into share_limit_journal (share_id, rx_bytes, tx_bytes, action) values ($1, $2, $3, $4) returning id") - if err != nil { - return 0, errors.Wrap(err, "error preparing share_limit_journal insert statement") - } - var id int - if err := stmt.QueryRow(j.ShareId, j.RxBytes, j.TxBytes, j.Action).Scan(&id); err != nil { - return 0, errors.Wrap(err, "error executing share_limit_journal insert statement") - } - return id, nil -} - -func (str *Store) IsShareLimitJournalEmpty(shrId int, trx *sqlx.Tx) (bool, error) { - count := 0 - if err := trx.QueryRowx("select count(0) from share_limit_journal where share_id = $1", shrId).Scan(&count); err != nil { - return false, err - } - return count == 0, nil -} - -func (str *Store) FindLatestShareLimitJournal(shrId int, trx *sqlx.Tx) (*ShareLimitJournal, error) { - j := &ShareLimitJournal{} - if err := trx.QueryRowx("select * from share_limit_journal where share_id = $1 order by created_at desc limit 1", shrId).StructScan(j); err != nil { - return nil, errors.Wrap(err, "error finding share_limit_journal by share_id") - } - return j, nil -} - -func (str *Store) FindSelectedLatestShareLimitjournal(shrIds []int, trx *sqlx.Tx) ([]*ShareLimitJournal, error) { - if len(shrIds) < 1 { - return nil, nil - } - in := "(" - for i := range shrIds { - if i > 0 { - in += ", " - } - in += fmt.Sprintf("%d", shrIds[i]) - } - in += ")" - rows, err := trx.Queryx("select id, share_id, rx_bytes, tx_bytes, action, created_at, updated_at from share_limit_journal where id in (select max(id) as id from share_limit_journal group by share_id) and share_id in " + in) - if err != nil { - return nil, errors.Wrap(err, "error selecting all latest share_limit_journal") - } - var sljs []*ShareLimitJournal - for rows.Next() { - slj := &ShareLimitJournal{} - if err := rows.StructScan(slj); err != nil { - return nil, errors.Wrap(err, "error scanning share_limit_journal") - } - sljs = append(sljs, slj) - } - return sljs, nil -} - -func (str *Store) FindAllLatestShareLimitJournal(trx *sqlx.Tx) ([]*ShareLimitJournal, error) { - rows, err := trx.Queryx("select id, share_id, rx_bytes, tx_bytes, action, created_at, updated_at from share_limit_journal where id in (select max(id) as id from share_limit_journal group by share_id)") - if err != nil { - return nil, errors.Wrap(err, "error selecting all latest share_limit_journal") - } - var sljs []*ShareLimitJournal - for rows.Next() { - slj := &ShareLimitJournal{} - if err := rows.StructScan(slj); err != nil { - return nil, errors.Wrap(err, "error scanning share_limit_journal") - } - sljs = append(sljs, slj) - } - return sljs, nil -} - -func (str *Store) DeleteShareLimitJournalForShare(shrId int, trx *sqlx.Tx) error { - if _, err := trx.Exec("delete from share_limit_journal where share_id = $1", shrId); err != nil { - return errors.Wrapf(err, "error deleting share_limit_journal for '#%d'", shrId) - } - return nil -} diff --git a/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql b/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql index c234d9a4..81582a91 100644 --- a/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql +++ b/controller/store/sql/postgresql/025_v0_4_31_bandwidth_limit_journal.sql @@ -10,10 +10,12 @@ create type limit_action_type as enum ('warning', 'limit'); create table bandwidth_limit_journal ( id serial primary key, account_id integer references accounts (id) not null, - limit_class integer references limit_classes, + limit_class_id integer references limit_classes (id), action limit_action_type not null, rx_bytes bigint not null, tx_bytes bigint not null, created_at timestamptz not null default(current_timestamp), updated_at timestamptz not null default(current_timestamp) -); \ No newline at end of file +); + +create index bandwidth_limit_journal_account_id_idx on bandwidth_limit_journal (account_id); \ No newline at end of file From d2d3adc5ccb15292e83823be303073ebbcef1c1a Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 31 May 2024 14:32:02 -0400 Subject: [PATCH 023/117] sqlite schema tweaks for bandwidth_limit_journal (#606) --- .../sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql b/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql index 6d9bd0fe..ffa03abb 100644 --- a/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql +++ b/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql @@ -11,6 +11,8 @@ create table bandwidth_limit_journal ( action string not null, rx_bytes bigint not null, tx_bytes bigint not null, - created_at timestamptz not null default(current_timestamp), - updated_at timestamptz not null default(current_timestamp) -); \ No newline at end of file + created_at timestamptz not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at timestamptz not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')) +); + +create index bandwidth_limit_journal_account_id_idx on bandwidth_limit_journal (account_id); \ No newline at end of file From b3f10dadec322443515d18436b44cd20a490bd09 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 31 May 2024 15:18:07 -0400 Subject: [PATCH 024/117] new limit journal test and necessary tweaks (#606) --- .../store/bandwidthLimitJournal_test.go | 63 +++++++++++++++++++ controller/store/limitClass.go | 2 +- .../sqlite3/022_v0_4_31_limits_classes.sql | 4 +- .../025_v0_4_31_bandwidth_limit_journal.sql | 8 +-- 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 controller/store/bandwidthLimitJournal_test.go diff --git a/controller/store/bandwidthLimitJournal_test.go b/controller/store/bandwidthLimitJournal_test.go new file mode 100644 index 00000000..254cff92 --- /dev/null +++ b/controller/store/bandwidthLimitJournal_test.go @@ -0,0 +1,63 @@ +package store + +import ( + "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestBandwidthLimitJournal(t *testing.T) { + str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"}) + assert.NoError(t, err) + assert.NotNil(t, str) + + trx, err := str.Begin() + assert.NoError(t, err) + assert.NotNil(t, trx) + + jEmpty, err := str.IsBandwidthLimitJournalEmpty(1, trx) + assert.NoError(t, err) + assert.True(t, jEmpty) + + acctId, err := str.CreateAccount(&Account{Email: "nobody@nowhere.com", Salt: "salt", Password: "password", Token: "token"}, trx) + assert.NoError(t, err) + + _, err = str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId, Action: WarningLimitAction, RxBytes: 1024, TxBytes: 2048}, trx) + assert.NoError(t, err) + + jEmpty, err = str.IsBandwidthLimitJournalEmpty(acctId, trx) + assert.NoError(t, err) + assert.False(t, jEmpty) + + latestJe, err := str.FindLatestBandwidthLimitJournal(acctId, trx) + assert.NoError(t, err) + assert.NotNil(t, latestJe) + assert.Nil(t, latestJe.LimitClassId) + assert.Equal(t, WarningLimitAction, latestJe.Action) + assert.Equal(t, int64(1024), latestJe.RxBytes) + assert.Equal(t, int64(2048), latestJe.TxBytes) + + lcId, err := str.CreateLimitClass(&LimitClass{ + LimitScope: AccountLimitScope, + LimitAction: LimitLimitAction, + ShareMode: sdk.PrivateShareMode, + BackendMode: sdk.VpnBackendMode, + PeriodMinutes: 60, + RxBytes: 4096, + TxBytes: 8192, + TotalBytes: 10240, + }, trx) + assert.NoError(t, err) + + _, err = str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId, LimitClassId: &lcId, Action: LimitLimitAction, RxBytes: 10240, TxBytes: 20480}, trx) + assert.NoError(t, err) + + latestJe, err = str.FindLatestBandwidthLimitJournal(acctId, trx) + assert.NoError(t, err) + assert.NotNil(t, latestJe) + assert.NotNil(t, latestJe.LimitClassId) + assert.Equal(t, lcId, *latestJe.LimitClassId) + assert.Equal(t, LimitLimitAction, latestJe.Action) + assert.Equal(t, int64(10240), latestJe.RxBytes) + assert.Equal(t, int64(20480), latestJe.TxBytes) +} diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 736bb44f..c5459868 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -37,7 +37,7 @@ func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { return 0, errors.Wrap(err, "error preparing limit_classes insert statement") } var id int - if err := stmt.QueryRow(lc.LimitScope, lc.LimitAction, lc.ShareMode, lc.BackendMode, lc.PeriodMinutes).Scan(&id); err != nil { + if err := stmt.QueryRow(lc.LimitScope, lc.LimitAction, lc.ShareMode, lc.BackendMode, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing limit_classes insert statement") } return id, nil diff --git a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql index c8de0c00..b88cf8a5 100644 --- a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql @@ -1,7 +1,7 @@ -- +migrate Up create table limit_classes ( - id serial primary key, + id integer primary key, limit_scope string not null default ('account'), limit_action string not null default ('limit'), @@ -23,7 +23,7 @@ create table limit_classes ( ); create table applied_limit_classes ( - id serial primary key, + id integer primary key, account_id integer not null references accounts (id), limit_class_id integer not null references limit_classes (id), created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), diff --git a/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql b/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql index ffa03abb..135a3478 100644 --- a/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql +++ b/controller/store/sql/sqlite3/025_v0_4_31_bandwidth_limit_journal.sql @@ -5,14 +5,14 @@ drop table environment_limit_journal; drop table share_limit_journal; create table bandwidth_limit_journal ( - id serial primary key, + id integer primary key, account_id integer references accounts (id) not null, - limit_class integer references limit_classes, + limit_class_id integer references limit_classes, action string not null, rx_bytes bigint not null, tx_bytes bigint not null, - created_at timestamptz not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), - updated_at timestamptz not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')) + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')) ); create index bandwidth_limit_journal_account_id_idx on bandwidth_limit_journal (account_id); \ No newline at end of file From 61e6953caf7895208b33ceff4816782f795dae91 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 31 May 2024 15:24:28 -0400 Subject: [PATCH 025/117] updated test for finding all tip limit journal entries (#606) --- .../store/bandwidthLimitJournal_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/controller/store/bandwidthLimitJournal_test.go b/controller/store/bandwidthLimitJournal_test.go index 254cff92..449a42d6 100644 --- a/controller/store/bandwidthLimitJournal_test.go +++ b/controller/store/bandwidthLimitJournal_test.go @@ -61,3 +61,35 @@ func TestBandwidthLimitJournal(t *testing.T) { assert.Equal(t, int64(10240), latestJe.RxBytes) assert.Equal(t, int64(20480), latestJe.TxBytes) } + +func TestFindAllBandwidthLimitJournal(t *testing.T) { + str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"}) + assert.Nil(t, err) + assert.NotNil(t, str) + + trx, err := str.Begin() + assert.Nil(t, err) + assert.NotNil(t, trx) + + acctId1, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt1", Password: "password1", Token: "token1", Limitless: false, Deleted: false}, trx) + assert.Nil(t, err) + + _, err = str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId1, Action: WarningLimitAction, RxBytes: 2048, TxBytes: 4096}, trx) + assert.Nil(t, err) + _, err = str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId1, Action: LimitLimitAction, RxBytes: 2048, TxBytes: 4096}, trx) + assert.Nil(t, err) + aljId13, err := str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId1, Action: LimitLimitAction, RxBytes: 8192, TxBytes: 10240}, trx) + assert.Nil(t, err) + + acctId2, err := str.CreateAccount(&Account{Email: "someone@somewhere.com", Salt: "salt2", Password: "password2", Token: "token2", Limitless: false, Deleted: false}, trx) + assert.Nil(t, err) + + aljId21, err := str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId2, Action: WarningLimitAction, RxBytes: 2048, TxBytes: 4096}, trx) + assert.Nil(t, err) + + aljs, err := str.FindAllLatestBandwidthLimitJournal(trx) + assert.Nil(t, err) + assert.Equal(t, 2, len(aljs)) + assert.Equal(t, aljId13, aljs[0].Id) + assert.Equal(t, aljId21, aljs[1].Id) +} From 22002477bef169c5918affe58f843111fd2ff675 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 31 May 2024 15:57:41 -0400 Subject: [PATCH 026/117] naming lint (#606) --- controller/limits/agent.go | 6 +++--- controller/limits/{accountLimitAction.go => limitAction.go} | 0 controller/limits/{accountRelaxAction.go => relaxAction.go} | 0 .../limits/{accountWarningAction.go => warningAction.go} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename controller/limits/{accountLimitAction.go => limitAction.go} (100%) rename controller/limits/{accountRelaxAction.go => relaxAction.go} (100%) rename controller/limits/{accountWarningAction.go => warningAction.go} (100%) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index d63a45c8..9f3d2c65 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -227,7 +227,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { return nil } - if enforce, warning, rxBytes, txBytes, err := a.checkAccountLimit(u.AccountId); err == nil { + if enforce, warning, rxBytes, txBytes, err := a.checkBandwidthLimit(u.AccountId); err == nil { if enforce { enforced := false var enforcedAt time.Time @@ -324,7 +324,7 @@ func (a *Agent) relax() error { for _, alj := range aljs { if acct, err := a.str.GetAccount(alj.AccountId, trx); err == nil { if alj.Action == store.WarningLimitAction || alj.Action == store.LimitLimitAction { - if enforce, warning, rxBytes, txBytes, err := a.checkAccountLimit(int64(alj.AccountId)); err == nil { + if enforce, warning, rxBytes, txBytes, err := a.checkBandwidthLimit(int64(alj.AccountId)); err == nil { if !enforce && !warning { if alj.Action == store.LimitLimitAction { // run relax actions for account @@ -365,7 +365,7 @@ func (a *Agent) relax() error { return nil } -func (a *Agent) checkAccountLimit(acctId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) { +func (a *Agent) checkBandwidthLimit(acctId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) { period := 24 * time.Hour limit := DefaultBandwidthPerPeriod() if a.cfg.Bandwidth != nil { diff --git a/controller/limits/accountLimitAction.go b/controller/limits/limitAction.go similarity index 100% rename from controller/limits/accountLimitAction.go rename to controller/limits/limitAction.go diff --git a/controller/limits/accountRelaxAction.go b/controller/limits/relaxAction.go similarity index 100% rename from controller/limits/accountRelaxAction.go rename to controller/limits/relaxAction.go diff --git a/controller/limits/accountWarningAction.go b/controller/limits/warningAction.go similarity index 100% rename from controller/limits/accountWarningAction.go rename to controller/limits/warningAction.go From 6256055a998480a86500a40843b66c69a9388c11 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 31 May 2024 16:06:42 -0400 Subject: [PATCH 027/117] inflow share details (#606) --- controller/limits/agent.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 9f3d2c65..07ee9728 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -227,6 +227,12 @@ func (a *Agent) enforce(u *metrics.Usage) error { return nil } + shr, err := a.str.FindShareWithToken(u.ShareToken, trx) + if err != nil { + return err + } + logrus.Infof("share: '%v', shareMode: '%v', backendMode: '%v'", shr.Token, shr.ShareMode, shr.BackendMode) + if enforce, warning, rxBytes, txBytes, err := a.checkBandwidthLimit(u.AccountId); err == nil { if enforce { enforced := false From cea7ff6474d53f4195c229c23512e6cf82f94f4f Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 3 Jun 2024 13:37:32 -0400 Subject: [PATCH 028/117] refinements and refactoring of limit classes to re-align with updated bandwidth journal, etc. (#606) --- controller/limits/agent.go | 48 +++++++++---------- controller/limits/limitClasses.go | 17 +------ .../store/bandwidthLimitJournal_test.go | 3 +- controller/store/limitClass.go | 8 ++-- ...sses.sql => 022_v0_4_31_limit_classes.sql} | 5 +- ...sses.sql => 022_v0_4_31_limit_classes.sql} | 6 +-- 6 files changed, 34 insertions(+), 53 deletions(-) rename controller/store/sql/postgresql/{022_v0_4_31_limits_classes.sql => 022_v0_4_31_limit_classes.sql} (91%) rename controller/store/sql/sqlite3/{022_v0_4_31_limits_classes.sql => 022_v0_4_31_limit_classes.sql} (91%) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 07ee9728..a7743391 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -14,30 +14,30 @@ import ( ) type Agent struct { - cfg *Config - ifx *influxReader - zCfg *zrokEdgeSdk.Config - str *store.Store - queue chan *metrics.Usage - acctWarningActions []AccountAction - acctLimitActions []AccountAction - acctRelaxActions []AccountAction - close chan struct{} - join chan struct{} + cfg *Config + ifx *influxReader + zCfg *zrokEdgeSdk.Config + str *store.Store + queue chan *metrics.Usage + warningActions []AccountAction + limitActions []AccountAction + relaxActions []AccountAction + close chan struct{} + join chan struct{} } func NewAgent(cfg *Config, ifxCfg *metrics.InfluxConfig, zCfg *zrokEdgeSdk.Config, emailCfg *emailUi.Config, str *store.Store) (*Agent, error) { a := &Agent{ - cfg: cfg, - ifx: newInfluxReader(ifxCfg), - zCfg: zCfg, - str: str, - queue: make(chan *metrics.Usage, 1024), - acctWarningActions: []AccountAction{newAccountWarningAction(emailCfg, str)}, - acctLimitActions: []AccountAction{newAccountLimitAction(str, zCfg)}, - acctRelaxActions: []AccountAction{newAccountRelaxAction(str, zCfg)}, - close: make(chan struct{}), - join: make(chan struct{}), + cfg: cfg, + ifx: newInfluxReader(ifxCfg), + zCfg: zCfg, + str: str, + queue: make(chan *metrics.Usage, 1024), + warningActions: []AccountAction{newAccountWarningAction(emailCfg, str)}, + limitActions: []AccountAction{newAccountLimitAction(str, zCfg)}, + relaxActions: []AccountAction{newAccountRelaxAction(str, zCfg)}, + close: make(chan struct{}), + join: make(chan struct{}), } return a, nil } @@ -258,8 +258,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { if err != nil { return err } - // run account limit actions - for _, action := range a.acctLimitActions { + for _, action := range a.limitActions { if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } @@ -295,8 +294,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { if err != nil { return err } - // run account warning actions - for _, action := range a.acctWarningActions { + for _, action := range a.warningActions { if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } @@ -334,7 +332,7 @@ func (a *Agent) relax() error { if !enforce && !warning { if alj.Action == store.LimitLimitAction { // run relax actions for account - for _, action := range a.acctRelaxActions { + for _, action := range a.relaxActions { if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } diff --git a/controller/limits/limitClasses.go b/controller/limits/limitClasses.go index 248b115e..95e0b15c 100644 --- a/controller/limits/limitClasses.go +++ b/controller/limits/limitClasses.go @@ -7,25 +7,10 @@ import ( func sortLimitClasses(lcs []*store.LimitClass) { sort.Slice(lcs, func(i, j int) bool { - ipoints := limitScopePoints(lcs[i]) + modePoints(lcs[i]) - jpoints := limitScopePoints(lcs[j]) + modePoints(lcs[j]) - return ipoints > jpoints + return modePoints(lcs[i]) > modePoints(lcs[j]) }) } -func limitScopePoints(lc *store.LimitClass) int { - points := 0 - switch lc.LimitScope { - case store.AccountLimitScope: - points += 1000 - case store.EnvironmentLimitScope: - points += 100 - case store.ShareLimitScope: - points += 10 - } - return points -} - func modePoints(lc *store.LimitClass) int { points := 0 if lc.BackendMode != "" { diff --git a/controller/store/bandwidthLimitJournal_test.go b/controller/store/bandwidthLimitJournal_test.go index 449a42d6..9354fc4b 100644 --- a/controller/store/bandwidthLimitJournal_test.go +++ b/controller/store/bandwidthLimitJournal_test.go @@ -38,14 +38,13 @@ func TestBandwidthLimitJournal(t *testing.T) { assert.Equal(t, int64(2048), latestJe.TxBytes) lcId, err := str.CreateLimitClass(&LimitClass{ - LimitScope: AccountLimitScope, - LimitAction: LimitLimitAction, ShareMode: sdk.PrivateShareMode, BackendMode: sdk.VpnBackendMode, PeriodMinutes: 60, RxBytes: 4096, TxBytes: 8192, TotalBytes: 10240, + LimitAction: LimitLimitAction, }, trx) assert.NoError(t, err) diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index c5459868..0290010d 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -9,10 +9,9 @@ import ( type LimitClass struct { Model - LimitScope LimitScope - LimitAction LimitAction ShareMode sdk.ShareMode BackendMode sdk.BackendMode + Environments int Shares int ReservedShares int UniqueNames int @@ -20,6 +19,7 @@ type LimitClass struct { RxBytes int64 TxBytes int64 TotalBytes int64 + LimitAction LimitAction } func (lc LimitClass) String() string { @@ -32,12 +32,12 @@ func (lc LimitClass) String() string { } func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into limit_classes (limit_scope, limit_action, share_mode, backend_mode, period_minutes, rx_bytes, tx_bytes, total_bytes) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id") + stmt, err := trx.Prepare("insert into limit_classes (share_mode, backend_mode, environments, shares, reserved_shares, unique_names, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing limit_classes insert statement") } var id int - if err := stmt.QueryRow(lc.LimitScope, lc.LimitAction, lc.ShareMode, lc.BackendMode, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes).Scan(&id); err != nil { + if err := stmt.QueryRow(lc.ShareMode, lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing limit_classes insert statement") } return id, nil diff --git a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql b/controller/store/sql/postgresql/022_v0_4_31_limit_classes.sql similarity index 91% rename from controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql rename to controller/store/sql/postgresql/022_v0_4_31_limit_classes.sql index 193626e2..59bf64dd 100644 --- a/controller/store/sql/postgresql/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/postgresql/022_v0_4_31_limit_classes.sql @@ -1,13 +1,10 @@ -- +migrate Up -create type limit_scope as enum ('account', 'environment', 'share'); create type limit_action as enum ('warning', 'limit'); create table limit_classes ( id serial primary key, - limit_scope limit_scope not null default ('account'), - limit_action limit_action not null default ('limit'), share_mode share_mode, backend_mode backend_mode, @@ -20,6 +17,8 @@ create table limit_classes ( tx_bytes bigint not null default (-1), total_bytes bigint not null default (-1), + limit_action limit_action not null default ('limit'), + created_at timestamptz not null default(current_timestamp), updated_at timestamptz not null default(current_timestamp), deleted boolean not null default(false) diff --git a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql b/controller/store/sql/sqlite3/022_v0_4_31_limit_classes.sql similarity index 91% rename from controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql rename to controller/store/sql/sqlite3/022_v0_4_31_limit_classes.sql index b88cf8a5..50d4a132 100644 --- a/controller/store/sql/sqlite3/022_v0_4_31_limits_classes.sql +++ b/controller/store/sql/sqlite3/022_v0_4_31_limit_classes.sql @@ -3,8 +3,6 @@ create table limit_classes ( id integer primary key, - limit_scope string not null default ('account'), - limit_action string not null default ('limit'), share_mode string, backend_mode string, @@ -17,13 +15,15 @@ create table limit_classes ( tx_bytes bigint not null default (-1), total_bytes bigint not null default (-1), + limit_action string not null default ('limit'), + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), deleted boolean not null default(false) ); create table applied_limit_classes ( - id integer primary key, + id integer primary key, account_id integer not null references accounts (id), limit_class_id integer not null references limit_classes (id), created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), From 8a255a0ee81d0ef0cf951f3c43f2b5ed946f67dd Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 3 Jun 2024 14:01:20 -0400 Subject: [PATCH 029/117] new bandwidth class interface to facilitate operating on global and class-based bandwidth limits in the same code, easily (#606) --- controller/limits/agent.go | 10 ------ controller/limits/limitClasses.go | 57 ++++++++++++++++++++++++------- controller/store/limitClass.go | 43 +++++++++++++++++++++++ 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index a7743391..ff0a5a56 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -98,16 +98,6 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s return false, err } - alc, err := a.str.FindLimitClassesForAccount(acctId, trx) - if err != nil { - logrus.Errorf("error finding limit classes for account with id '%d': %v", acctId, err) - return false, err - } - sortLimitClasses(alc) - if len(alc) > 0 { - logrus.Infof("selected limit class: %v", alc[0]) - } - if a.cfg.Shares > Unlimited || (reserved && a.cfg.ReservedShares > Unlimited) || (reserved && uniqueName && a.cfg.UniqueNames > Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { diff --git a/controller/limits/limitClasses.go b/controller/limits/limitClasses.go index 95e0b15c..24f319dc 100644 --- a/controller/limits/limitClasses.go +++ b/controller/limits/limitClasses.go @@ -2,22 +2,53 @@ package limits import ( "github.com/openziti/zrok/controller/store" - "sort" + "github.com/openziti/zrok/sdk/golang/sdk" ) -func sortLimitClasses(lcs []*store.LimitClass) { - sort.Slice(lcs, func(i, j int) bool { - return modePoints(lcs[i]) > modePoints(lcs[j]) - }) +type configBandwidthClass struct { + periodInMinutes int + bw *Bandwidth + limitAction store.LimitAction } -func modePoints(lc *store.LimitClass) int { - points := 0 - if lc.BackendMode != "" { - points += 1 +func newConfigBandwidthClasses(cfgClass *BandwidthPerPeriod) []store.BandwidthClass { + return []store.BandwidthClass{ + &configBandwidthClass{ + periodInMinutes: int(cfgClass.Period.Minutes()), + bw: cfgClass.Warning, + limitAction: store.WarningLimitAction, + }, } - if lc.ShareMode != "" { - points += 1 - } - return points +} + +func (bc *configBandwidthClass) IsGlobal() bool { + return true +} + +func (bc *configBandwidthClass) GetShareMode() sdk.ShareMode { + return "" +} + +func (bc *configBandwidthClass) GetBackendMode() sdk.BackendMode { + return "" +} + +func (bc *configBandwidthClass) GetPeriodMinutes() int { + return bc.periodInMinutes +} + +func (bc *configBandwidthClass) GetRxBytes() int64 { + return bc.bw.Rx +} + +func (bc *configBandwidthClass) GetTxBytes() int64 { + return bc.bw.Tx +} + +func (bc *configBandwidthClass) GetTotalBytes() int64 { + return bc.bw.Total +} + +func (bc *configBandwidthClass) GetLimitAction() store.LimitAction { + return bc.limitAction } diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 0290010d..9f9ae05e 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -7,6 +7,17 @@ import ( "github.com/pkg/errors" ) +type BandwidthClass interface { + IsGlobal() bool + GetShareMode() sdk.ShareMode + GetBackendMode() sdk.BackendMode + GetPeriodMinutes() int + GetRxBytes() int64 + GetTxBytes() int64 + GetTotalBytes() int64 + GetLimitAction() LimitAction +} + type LimitClass struct { Model ShareMode sdk.ShareMode @@ -22,6 +33,38 @@ type LimitClass struct { LimitAction LimitAction } +func (lc *LimitClass) IsGlobal() bool { + return false +} + +func (lc *LimitClass) GetShareMode() sdk.ShareMode { + return lc.ShareMode +} + +func (lc *LimitClass) GetBackendMode() sdk.BackendMode { + return lc.BackendMode +} + +func (lc *LimitClass) GetPeriodMinutes() int { + return lc.PeriodMinutes +} + +func (lc *LimitClass) GetRxBytes() int64 { + return lc.RxBytes +} + +func (lc *LimitClass) GetTxBytes() int64 { + return lc.TxBytes +} + +func (lc *LimitClass) GetTotalBytes() int64 { + return lc.TotalBytes +} + +func (lc *LimitClass) GetLimitAction() LimitAction { + return lc.LimitAction +} + func (lc LimitClass) String() string { out, err := json.MarshalIndent(&lc, "", " ") if err != nil { From db71e06610d3987e8455716a514a8db54e713386 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 3 Jun 2024 14:06:07 -0400 Subject: [PATCH 030/117] include both warning and limits globals (#606) --- controller/limits/limitClasses.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/controller/limits/limitClasses.go b/controller/limits/limitClasses.go index 24f319dc..e6a22c19 100644 --- a/controller/limits/limitClasses.go +++ b/controller/limits/limitClasses.go @@ -11,13 +11,18 @@ type configBandwidthClass struct { limitAction store.LimitAction } -func newConfigBandwidthClasses(cfgClass *BandwidthPerPeriod) []store.BandwidthClass { +func newConfigBandwidthClasses(cfg *BandwidthPerPeriod) []store.BandwidthClass { return []store.BandwidthClass{ &configBandwidthClass{ - periodInMinutes: int(cfgClass.Period.Minutes()), - bw: cfgClass.Warning, + periodInMinutes: int(cfg.Period.Minutes()), + bw: cfg.Warning, limitAction: store.WarningLimitAction, }, + &configBandwidthClass{ + periodInMinutes: int(cfg.Period.Minutes()), + bw: cfg.Limit, + limitAction: store.LimitLimitAction, + }, } } From 9a84975b4aa1c418b3d15c31d1210c38fafb9e45 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 4 Jun 2024 10:33:39 -0400 Subject: [PATCH 031/117] naming lint; streamlining limits agent processing (#606) --- controller/limits/agent.go | 6 +++--- controller/limits/limitAction.go | 8 ++++---- controller/limits/relaxAction.go | 8 ++++---- controller/limits/warningAction.go | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index ff0a5a56..504da8fe 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -33,9 +33,9 @@ func NewAgent(cfg *Config, ifxCfg *metrics.InfluxConfig, zCfg *zrokEdgeSdk.Confi zCfg: zCfg, str: str, queue: make(chan *metrics.Usage, 1024), - warningActions: []AccountAction{newAccountWarningAction(emailCfg, str)}, - limitActions: []AccountAction{newAccountLimitAction(str, zCfg)}, - relaxActions: []AccountAction{newAccountRelaxAction(str, zCfg)}, + warningActions: []AccountAction{newWarningAction(emailCfg, str)}, + limitActions: []AccountAction{newLimitAction(str, zCfg)}, + relaxActions: []AccountAction{newRelaxAction(str, zCfg)}, close: make(chan struct{}), join: make(chan struct{}), } diff --git a/controller/limits/limitAction.go b/controller/limits/limitAction.go index fe3561bd..d4e5ee05 100644 --- a/controller/limits/limitAction.go +++ b/controller/limits/limitAction.go @@ -8,16 +8,16 @@ import ( "github.com/sirupsen/logrus" ) -type accountLimitAction struct { +type limitAction struct { str *store.Store zCfg *zrokEdgeSdk.Config } -func newAccountLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *accountLimitAction { - return &accountLimitAction{str, zCfg} +func newLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *limitAction { + return &limitAction{str, zCfg} } -func (a *accountLimitAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { +func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { logrus.Infof("limiting '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) diff --git a/controller/limits/relaxAction.go b/controller/limits/relaxAction.go index d2b32380..db3c33a4 100644 --- a/controller/limits/relaxAction.go +++ b/controller/limits/relaxAction.go @@ -10,16 +10,16 @@ import ( "github.com/sirupsen/logrus" ) -type accountRelaxAction struct { +type relaxAction struct { str *store.Store zCfg *zrokEdgeSdk.Config } -func newAccountRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *accountRelaxAction { - return &accountRelaxAction{str, zCfg} +func newRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *relaxAction { + return &relaxAction{str, zCfg} } -func (a *accountRelaxAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { +func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { logrus.Infof("relaxing '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) diff --git a/controller/limits/warningAction.go b/controller/limits/warningAction.go index 4b2610bf..829f629c 100644 --- a/controller/limits/warningAction.go +++ b/controller/limits/warningAction.go @@ -9,16 +9,16 @@ import ( "github.com/sirupsen/logrus" ) -type accountWarningAction struct { +type warningAction struct { str *store.Store cfg *emailUi.Config } -func newAccountWarningAction(cfg *emailUi.Config, str *store.Store) *accountWarningAction { - return &accountWarningAction{str, cfg} +func newWarningAction(cfg *emailUi.Config, str *store.Store) *warningAction { + return &warningAction{str, cfg} } -func (a *accountWarningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, limit *BandwidthPerPeriod, _ *sqlx.Tx) error { +func (a *warningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, limit *BandwidthPerPeriod, _ *sqlx.Tx) error { logrus.Infof("warning '%v'", acct.Email) if a.cfg != nil { From 97dbd197d64388553a89e86b4423ade84785bc3b Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 4 Jun 2024 14:06:44 -0400 Subject: [PATCH 032/117] massive bandwidth limits rewrite to support limit classes (#606) --- controller/limits/agent.go | 337 +++++++++++++--------- controller/limits/limitAction.go | 2 +- controller/limits/limitClasses.go | 4 + controller/limits/model.go | 6 +- controller/limits/relaxAction.go | 2 +- controller/limits/warningAction.go | 19 +- controller/store/appliedLimitClass.go | 2 +- controller/store/bandwidthLimitJournal.go | 55 ++++ controller/store/limitClass.go | 31 +- controller/store/share.go | 8 + 10 files changed, 314 insertions(+), 152 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 504da8fe..86da6240 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -217,87 +217,82 @@ func (a *Agent) enforce(u *metrics.Usage) error { return nil } - shr, err := a.str.FindShareWithToken(u.ShareToken, trx) + shr, err := a.str.FindShareWithTokenEvenIfDeleted(u.ShareToken, trx) if err != nil { return err } - logrus.Infof("share: '%v', shareMode: '%v', backendMode: '%v'", shr.Token, shr.ShareMode, shr.BackendMode) + logrus.Debugf("share: '%v', shareMode: '%v', backendMode: '%v'", shr.Token, shr.ShareMode, shr.BackendMode) - if enforce, warning, rxBytes, txBytes, err := a.checkBandwidthLimit(u.AccountId); err == nil { - if enforce { - enforced := false - var enforcedAt time.Time - if empty, err := a.str.IsBandwidthLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestBandwidthLimitJournal(int(u.AccountId), trx); err == nil { - enforced = latest.Action == store.LimitLimitAction + alcs, err := a.str.FindAppliedLimitClassesForAccount(int(u.AccountId), trx) + if err != nil { + return err + } + exceededLc, rxBytes, txBytes, err := a.isOverLimitClass(u, alcs) + if err != nil { + return errors.Wrap(err, "error checking limit classes") + } + + if exceededLc != nil { + enforced := false + var enforcedAt time.Time + + if exceededLc.IsGlobal() { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(int(u.AccountId), trx); err == nil && !empty { + if latest, err := a.str.FindLatestBandwidthLimitJournalForGlobal(int(u.AccountId), trx); err == nil { + enforced = latest.Action == exceededLc.GetLimitAction() enforcedAt = latest.UpdatedAt } } - - if !enforced { - _, err := a.str.CreateBandwidthLimitJournalEntry(&store.BandwidthLimitJournalEntry{ - AccountId: int(u.AccountId), - RxBytes: rxBytes, - TxBytes: txBytes, - Action: store.LimitLimitAction, - }, trx) - if err != nil { - return err + } else { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(int(u.AccountId), exceededLc.GetLimitClassId(), trx); err == nil && !empty { + if latest, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(int(u.AccountId), exceededLc.GetLimitClassId(), trx); err == nil { + enforced = latest.Action == exceededLc.GetLimitAction() + enforcedAt = latest.UpdatedAt } - acct, err := a.str.GetAccount(int(u.AccountId), trx) - if err != nil { - return err - } - for _, action := range a.limitActions { - if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - if err := trx.Commit(); err != nil { - return err - } - } else { - logrus.Debugf("already enforced limit for account '#%d' at %v", u.AccountId, enforcedAt) - } - - } else if warning { - warned := false - var warnedAt time.Time - if empty, err := a.str.IsBandwidthLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestBandwidthLimitJournal(int(u.AccountId), trx); err == nil { - warned = latest.Action == store.WarningLimitAction || latest.Action == store.LimitLimitAction - warnedAt = latest.UpdatedAt - } - } - - if !warned { - _, err := a.str.CreateBandwidthLimitJournalEntry(&store.BandwidthLimitJournalEntry{ - AccountId: int(u.AccountId), - RxBytes: rxBytes, - TxBytes: txBytes, - Action: store.WarningLimitAction, - }, trx) - if err != nil { - return err - } - acct, err := a.str.GetAccount(int(u.AccountId), trx) - if err != nil { - return err - } - for _, action := range a.warningActions { - if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - if err := trx.Commit(); err != nil { - return err - } - } else { - logrus.Debugf("already warned account '#%d' at %v", u.AccountId, warnedAt) } } - } else { - logrus.Error(err) + + if !enforced { + je := &store.BandwidthLimitJournalEntry{ + AccountId: int(u.AccountId), + RxBytes: rxBytes, + TxBytes: txBytes, + Action: exceededLc.GetLimitAction(), + } + if !exceededLc.IsGlobal() { + lcId := exceededLc.GetLimitClassId() + je.LimitClassId = &lcId + } + _, err := a.str.CreateBandwidthLimitJournalEntry(je, trx) + + if err != nil { + return err + } + acct, err := a.str.GetAccount(int(u.AccountId), trx) + if err != nil { + return err + } + switch exceededLc.GetLimitAction() { + case store.LimitLimitAction: + for _, limitAction := range a.limitActions { + if err := limitAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, trx); err != nil { + return errors.Wrapf(err, "%v", reflect.TypeOf(limitAction).String()) + } + } + + case store.WarningLimitAction: + for _, warningAction := range a.warningActions { + if err := warningAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, trx); err != nil { + return errors.Wrapf(err, "%v", reflect.TypeOf(warningAction).String()) + } + } + } + if err := trx.Commit(); err != nil { + return err + } + } else { + logrus.Debugf("already enforced limit for account '%d' at %v", u.AccountId, enforcedAt) + } } return nil @@ -314,36 +309,77 @@ func (a *Agent) relax() error { commit := false - if aljs, err := a.str.FindAllLatestBandwidthLimitJournal(trx); err == nil { - for _, alj := range aljs { - if acct, err := a.str.GetAccount(alj.AccountId, trx); err == nil { - if alj.Action == store.WarningLimitAction || alj.Action == store.LimitLimitAction { - if enforce, warning, rxBytes, txBytes, err := a.checkBandwidthLimit(int64(alj.AccountId)); err == nil { - if !enforce && !warning { - if alj.Action == store.LimitLimitAction { - // run relax actions for account - for _, action := range a.relaxActions { - if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth, trx); err != nil { - return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) - } - } - } else { - logrus.Infof("relaxing warning for '%v'", acct.Email) - } - if err := a.str.DeleteBandwidthLimitJournal(acct.Id, trx); err == nil { - commit = true - } else { - logrus.Errorf("error deleting account_limit_journal for '%v': %v", acct.Email, err) - } - } else { - logrus.Infof("account '%v' still over limit", acct.Email) - } - } else { - logrus.Errorf("error checking account limit for '%v': %v", acct.Email, err) - } + if bwjes, err := a.str.FindAllBandwidthLimitJournal(trx); err == nil { + periodBw := make(map[int]struct { + rx int64 + tx int64 + }) + + accounts := make(map[int]*store.Account) + + for _, bwje := range bwjes { + if _, found := accounts[bwje.AccountId]; !found { + if acct, err := a.str.GetAccount(bwje.AccountId, trx); err == nil { + accounts[bwje.AccountId] = acct + } else { + return err + } + } + + var bwc store.BandwidthClass + if bwje.LimitClassId != nil { + globalBwcs := newConfigBandwidthClasses(a.cfg.Bandwidth) + if bwje.Action == store.WarningLimitAction { + bwc = globalBwcs[0] + } else { + bwc = globalBwcs[1] } } else { - logrus.Errorf("error getting account for '#%d': %v", alj.AccountId, err) + lc, err := a.str.GetLimitClass(*bwje.LimitClassId, trx) + if err != nil { + return err + } + bwc = lc + } + + if _, found := periodBw[bwc.GetPeriodMinutes()]; !found { + rx, tx, err := a.ifx.totalRxTxForAccount(int64(bwje.AccountId), time.Duration(bwc.GetPeriodMinutes())*time.Minute) + if err != nil { + return err + } + periodBw[bwc.GetPeriodMinutes()] = struct { + rx int64 + tx int64 + }{ + rx: rx, + tx: tx, + } + } + + used := periodBw[bwc.GetPeriodMinutes()] + if !a.limitExceeded(used.rx, used.tx, bwc) { + if bwc.GetLimitAction() == store.LimitLimitAction { + for _, action := range a.relaxActions { + if err := action.HandleAccount(accounts[bwje.AccountId], used.rx, used.tx, bwc, trx); err != nil { + return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) + } + } + } else { + logrus.Infof("relaxing warning for '%v'", accounts[bwje.AccountId].Email) + } + var lcId *int + if !bwc.IsGlobal() { + newLcId := 0 + newLcId = bwc.GetLimitClassId() + lcId = &newLcId + } + if err := a.str.DeleteBandwidthLimitJournalEntryForLimitClass(bwje.AccountId, lcId, trx); err == nil { + commit = true + } else { + logrus.Errorf("error deleting bandwidth limit journal entry for '%v': %v", accounts[bwje.AccountId].Email, err) + } + } else { + logrus.Infof("account '%v' still over limit: %v", accounts[bwje.AccountId].Email, bwc) } } } else { @@ -359,44 +395,87 @@ func (a *Agent) relax() error { return nil } -func (a *Agent) checkBandwidthLimit(acctId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) { - period := 24 * time.Hour - limit := DefaultBandwidthPerPeriod() - if a.cfg.Bandwidth != nil { - limit = a.cfg.Bandwidth +func (a *Agent) isOverLimitClass(u *metrics.Usage, alcs []*store.LimitClass) (store.BandwidthClass, int64, int64, error) { + periodBw := make(map[int]struct { + rx int64 + tx int64 + }) + + var allBwcs []store.BandwidthClass + for _, alc := range alcs { + allBwcs = append(allBwcs, alc) } - if limit.Period > 0 { - period = limit.Period - } - rx, tx, err := a.ifx.totalRxTxForAccount(acctId, period) - if err != nil { - logrus.Error(err) + for _, globBwc := range newConfigBandwidthClasses(a.cfg.Bandwidth) { + allBwcs = append(allBwcs, globBwc) } - enforce, warning = a.checkLimit(limit, rx, tx) - return enforce, warning, rx, tx, nil + // find period data for each class + for _, bwc := range allBwcs { + if _, found := periodBw[bwc.GetPeriodMinutes()]; !found { + rx, tx, err := a.ifx.totalRxTxForAccount(u.AccountId, time.Minute*time.Duration(bwc.GetPeriodMinutes())) + if err != nil { + return nil, 0, 0, errors.Wrapf(err, "error getting rx/tx for account '%d'", u.AccountId) + } + periodBw[bwc.GetPeriodMinutes()] = struct { + rx int64 + tx int64 + }{ + rx: rx, + tx: tx, + } + } + } + + // find the highest, most specific limit class that has been exceeded + var selectedLc store.BandwidthClass + selectedLcPoints := -1 + var rxBytes int64 + var txBytes int64 + for _, bwc := range allBwcs { + points := a.bandwidthClassPoints(bwc) + if points >= selectedLcPoints { + period := periodBw[bwc.GetPeriodMinutes()] + if a.limitExceeded(period.rx, period.tx, bwc) { + selectedLc = bwc + selectedLcPoints = points + rxBytes = period.rx + txBytes = period.tx + } + } + } + + return selectedLc, rxBytes, txBytes, nil } -func (a *Agent) checkLimit(cfg *BandwidthPerPeriod, rx, tx int64) (enforce, warning bool) { - if cfg.Limit.Rx != Unlimited && rx > cfg.Limit.Rx { - return true, false +func (a *Agent) bandwidthClassPoints(bwc store.BandwidthClass) int { + points := 0 + if !bwc.IsGlobal() { + points++ } - if cfg.Limit.Tx != Unlimited && tx > cfg.Limit.Tx { - return true, false + if bwc.GetLimitAction() == store.WarningLimitAction { + points++ } - if cfg.Limit.Total != Unlimited && rx+tx > cfg.Limit.Total { - return true, false + if bwc.GetLimitAction() == store.LimitLimitAction { + points += 2 } - - if cfg.Warning.Rx != Unlimited && rx > cfg.Warning.Rx { - return false, true + if bwc.GetShareMode() != "" { + points += 5 } - if cfg.Warning.Tx != Unlimited && tx > cfg.Warning.Tx { - return false, true + if bwc.GetBackendMode() != "" { + points += 10 } - if cfg.Warning.Total != Unlimited && rx+tx > cfg.Warning.Total { - return false, true - } - - return false, false + return points +} + +func (a *Agent) limitExceeded(rx, tx int64, bwc store.BandwidthClass) bool { + if bwc.GetTxBytes() != Unlimited && tx >= bwc.GetTxBytes() { + return true + } + if bwc.GetRxBytes() != Unlimited && rx >= bwc.GetRxBytes() { + return true + } + if bwc.GetTxBytes() != Unlimited && bwc.GetRxBytes() != Unlimited && tx+rx >= bwc.GetTxBytes()+bwc.GetRxBytes() { + return true + } + return false } diff --git a/controller/limits/limitAction.go b/controller/limits/limitAction.go index d4e5ee05..6b857ee0 100644 --- a/controller/limits/limitAction.go +++ b/controller/limits/limitAction.go @@ -17,7 +17,7 @@ func newLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *limitAction { return &limitAction{str, zCfg} } -func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { +func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, _ store.BandwidthClass, trx *sqlx.Tx) error { logrus.Infof("limiting '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) diff --git a/controller/limits/limitClasses.go b/controller/limits/limitClasses.go index e6a22c19..a2d11c19 100644 --- a/controller/limits/limitClasses.go +++ b/controller/limits/limitClasses.go @@ -30,6 +30,10 @@ func (bc *configBandwidthClass) IsGlobal() bool { return true } +func (bc *configBandwidthClass) GetLimitClassId() int { + return -1 +} + func (bc *configBandwidthClass) GetShareMode() sdk.ShareMode { return "" } diff --git a/controller/limits/model.go b/controller/limits/model.go index c13f8aae..7044c7d4 100644 --- a/controller/limits/model.go +++ b/controller/limits/model.go @@ -6,13 +6,13 @@ import ( ) type AccountAction interface { - HandleAccount(a *store.Account, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error + HandleAccount(a *store.Account, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error } type EnvironmentAction interface { - HandleEnvironment(e *store.Environment, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error + HandleEnvironment(e *store.Environment, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error } type ShareAction interface { - HandleShare(s *store.Share, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error + HandleShare(s *store.Share, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error } diff --git a/controller/limits/relaxAction.go b/controller/limits/relaxAction.go index db3c33a4..7700e3e2 100644 --- a/controller/limits/relaxAction.go +++ b/controller/limits/relaxAction.go @@ -19,7 +19,7 @@ func newRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *relaxAction { return &relaxAction{str, zCfg} } -func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error { +func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, _ store.BandwidthClass, trx *sqlx.Tx) error { logrus.Infof("relaxing '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) diff --git a/controller/limits/warningAction.go b/controller/limits/warningAction.go index 829f629c..8e1841fd 100644 --- a/controller/limits/warningAction.go +++ b/controller/limits/warningAction.go @@ -7,6 +7,7 @@ import ( "github.com/openziti/zrok/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "time" ) type warningAction struct { @@ -18,27 +19,27 @@ func newWarningAction(cfg *emailUi.Config, str *store.Store) *warningAction { return &warningAction{str, cfg} } -func (a *warningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, limit *BandwidthPerPeriod, _ *sqlx.Tx) error { +func (a *warningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, limit store.BandwidthClass, _ *sqlx.Tx) error { logrus.Infof("warning '%v'", acct.Email) if a.cfg != nil { rxLimit := "(unlimited bytes)" - if limit.Limit.Rx != Unlimited { - rxLimit = util.BytesToSize(limit.Limit.Rx) + if limit.GetRxBytes() != Unlimited { + rxLimit = util.BytesToSize(limit.GetRxBytes()) } txLimit := "(unlimited bytes)" - if limit.Limit.Tx != Unlimited { - txLimit = util.BytesToSize(limit.Limit.Tx) + if limit.GetTxBytes() != Unlimited { + txLimit = util.BytesToSize(limit.GetTxBytes()) } totalLimit := "(unlimited bytes)" - if limit.Limit.Total != Unlimited { - totalLimit = util.BytesToSize(limit.Limit.Total) + if limit.GetTotalBytes() != Unlimited { + totalLimit = util.BytesToSize(limit.GetTotalBytes()) } detail := newDetailMessage() detail = detail.append("Your account has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes)) - detail = detail.append("This zrok instance only allows an account to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, limit.Period) - detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit)", limit.Period) + detail = detail.append("This zrok instance only allows an account to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, time.Duration(limit.GetPeriodMinutes())*time.Minute) + detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit)", time.Duration(limit.GetPeriodMinutes())*time.Minute) if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil { return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email) diff --git a/controller/store/appliedLimitClass.go b/controller/store/appliedLimitClass.go index 56c62aa2..edf25b61 100644 --- a/controller/store/appliedLimitClass.go +++ b/controller/store/appliedLimitClass.go @@ -23,7 +23,7 @@ func (str *Store) ApplyLimitClass(lc *AppliedLimitClass, trx *sqlx.Tx) (int, err return id, nil } -func (str *Store) FindLimitClassesForAccount(acctId int, trx *sqlx.Tx) ([]*LimitClass, error) { +func (str *Store) FindAppliedLimitClassesForAccount(acctId int, trx *sqlx.Tx) ([]*LimitClass, error) { rows, err := trx.Queryx("select limit_classes.* from applied_limit_classes, limit_classes where applied_limit_classes.account_id = $1 and applied_limit_classes.limit_class_id = limit_classes.id", acctId) if err != nil { return nil, errors.Wrap(err, "error finding limit classes for account") diff --git a/controller/store/bandwidthLimitJournal.go b/controller/store/bandwidthLimitJournal.go index eaeeb6eb..e3fb43c3 100644 --- a/controller/store/bandwidthLimitJournal.go +++ b/controller/store/bandwidthLimitJournal.go @@ -42,6 +42,54 @@ func (str *Store) FindLatestBandwidthLimitJournal(acctId int, trx *sqlx.Tx) (*Ba return j, nil } +func (str *Store) IsBandwidthLimitJournalEmptyForGlobal(acctId int, trx *sqlx.Tx) (bool, error) { + count := 0 + if err := trx.QueryRowx("select count(0) from bandwidth_limit_journal where account_id = $1 and limit_class_id is null", acctId).Scan(&count); err != nil { + return false, err + } + return count == 0, nil +} + +func (str *Store) FindLatestBandwidthLimitJournalForGlobal(acctId int, trx *sqlx.Tx) (*BandwidthLimitJournalEntry, error) { + j := &BandwidthLimitJournalEntry{} + if err := trx.QueryRowx("select * from bandwidth_limit_journal where account_id = $1 and limit_class_id is null order by id desc limit 1", acctId).Scan(&j); err != nil { + return nil, errors.Wrap(err, "error finding bandwidth_limit_journal by account_id for global") + } + return j, nil +} + +func (str *Store) IsBandwidthLimitJournalEmptyForLimitClass(acctId, lcId int, trx *sqlx.Tx) (bool, error) { + count := 0 + if err := trx.QueryRowx("select count(0) from bandwidth_limit_journal where account_id = $1 and limit_class_id = $2", acctId, lcId).Scan(&count); err != nil { + return false, err + } + return count == 0, nil +} + +func (str *Store) FindLatestBandwidthLimitJournalForLimitClass(acctId, lcId int, trx *sqlx.Tx) (*BandwidthLimitJournalEntry, error) { + j := &BandwidthLimitJournalEntry{} + if err := trx.QueryRowx("select * from bandwidth_limit_journal where account_id = $1 and limit_class_id = $2 order by id desc limit 1", acctId, lcId).StructScan(j); err != nil { + return nil, errors.Wrap(err, "error finding bandwidth_limit_journal by account_id and limit_class_id") + } + return j, nil +} + +func (str *Store) FindAllBandwidthLimitJournal(trx *sqlx.Tx) ([]*BandwidthLimitJournalEntry, error) { + rows, err := trx.Queryx("select * from bandwidth_limit_journal") + if err != nil { + return nil, errors.Wrap(err, "error finding all from bandwidth_limit_journal") + } + var jes []*BandwidthLimitJournalEntry + for rows.Next() { + je := &BandwidthLimitJournalEntry{} + if err := rows.StructScan(je); err != nil { + return nil, errors.Wrap(err, "error scanning bandwidth_limit_journal") + } + jes = append(jes, je) + } + return jes, nil +} + func (str *Store) FindAllLatestBandwidthLimitJournal(trx *sqlx.Tx) ([]*BandwidthLimitJournalEntry, error) { rows, err := trx.Queryx("select id, account_id, limit_class_id, action, rx_bytes, tx_bytes, created_at, updated_at from bandwidth_limit_journal where id in (select max(id) as id from bandwidth_limit_journal group by account_id)") if err != nil { @@ -64,3 +112,10 @@ func (str *Store) DeleteBandwidthLimitJournal(acctId int, trx *sqlx.Tx) error { } return nil } + +func (str *Store) DeleteBandwidthLimitJournalEntryForLimitClass(acctId int, lcId *int, trx *sqlx.Tx) error { + if _, err := trx.Exec("delete from bandwidth_limit_journal where account_id = $1 and limit_class_id = $2", acctId, lcId); err != nil { + return errors.Wrapf(err, "error deleting from bandwidth_limit_journal for account_id = %d and limit_class_id = %d", acctId, lcId) + } + return nil +} diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 9f9ae05e..f9a64e86 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -9,6 +9,7 @@ import ( type BandwidthClass interface { IsGlobal() bool + GetLimitClassId() int GetShareMode() sdk.ShareMode GetBackendMode() sdk.BackendMode GetPeriodMinutes() int @@ -33,35 +34,39 @@ type LimitClass struct { LimitAction LimitAction } -func (lc *LimitClass) IsGlobal() bool { +func (lc LimitClass) IsGlobal() bool { return false } -func (lc *LimitClass) GetShareMode() sdk.ShareMode { +func (lc LimitClass) GetLimitClassId() int { + return lc.Id +} + +func (lc LimitClass) GetShareMode() sdk.ShareMode { return lc.ShareMode } -func (lc *LimitClass) GetBackendMode() sdk.BackendMode { +func (lc LimitClass) GetBackendMode() sdk.BackendMode { return lc.BackendMode } -func (lc *LimitClass) GetPeriodMinutes() int { +func (lc LimitClass) GetPeriodMinutes() int { return lc.PeriodMinutes } -func (lc *LimitClass) GetRxBytes() int64 { +func (lc LimitClass) GetRxBytes() int64 { return lc.RxBytes } -func (lc *LimitClass) GetTxBytes() int64 { +func (lc LimitClass) GetTxBytes() int64 { return lc.TxBytes } -func (lc *LimitClass) GetTotalBytes() int64 { +func (lc LimitClass) GetTotalBytes() int64 { return lc.TotalBytes } -func (lc *LimitClass) GetLimitAction() LimitAction { +func (lc LimitClass) GetLimitAction() LimitAction { return lc.LimitAction } @@ -74,6 +79,8 @@ func (lc LimitClass) String() string { return string(out) } +var _ BandwidthClass = (*LimitClass)(nil) + func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { stmt, err := trx.Prepare("insert into limit_classes (share_mode, backend_mode, environments, shares, reserved_shares, unique_names, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") if err != nil { @@ -85,3 +92,11 @@ func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { } return id, nil } + +func (str *Store) GetLimitClass(lcId int, trx *sqlx.Tx) (*LimitClass, error) { + lc := &LimitClass{} + if err := trx.QueryRowx("select * from limit_classes where id = $1", lcId).StructScan(lc); err != nil { + return nil, errors.Wrap(err, "error selecting limit_class by id") + } + return lc, nil +} diff --git a/controller/store/share.go b/controller/store/share.go index 305d76e3..c12aa529 100644 --- a/controller/store/share.go +++ b/controller/store/share.go @@ -65,6 +65,14 @@ func (str *Store) FindShareWithToken(shrToken string, tx *sqlx.Tx) (*Share, erro return shr, nil } +func (str *Store) FindShareWithTokenEvenIfDeleted(shrToken string, tx *sqlx.Tx) (*Share, error) { + shr := &Share{} + if err := tx.QueryRowx("select * from shares where token = $1", shrToken).StructScan(shr); err != nil { + return nil, errors.Wrap(err, "error selecting share by token, even if deleted") + } + return shr, nil +} + func (str *Store) ShareWithTokenExists(shrToken string, tx *sqlx.Tx) (bool, error) { count := 0 if err := tx.QueryRowx("select count(0) from shares where token = $1 and not deleted", shrToken).Scan(&count); err != nil { From 97c2cd79d26b9e0d33476e17ddb0ce580c651cd1 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 4 Jun 2024 16:27:05 -0400 Subject: [PATCH 033/117] updated CanCreateEnvironment for limit classes (#606) --- controller/limits/agent.go | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 86da6240..7f7c3e4f 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -56,19 +56,43 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err } - if empty, err := a.str.IsBandwidthLimitJournalEmpty(acctId, trx); err == nil && !empty { - alj, err := a.str.FindLatestBandwidthLimitJournal(acctId, trx) - if err != nil { - return false, err - } - if alj.Action == store.LimitLimitAction { - return false, nil - } - } else if err != nil { + + alcs, err := a.str.FindAppliedLimitClassesForAccount(acctId, trx) + if err != nil { return false, err } + maxEnvironments := a.cfg.Environments + var lcId *int + for _, alc := range alcs { + if alc.ShareMode == "" && alc.BackendMode == "" && alc.Environments > maxEnvironments { + maxEnvironments = alc.Environments + lcId = &alc.Id + } + } - if a.cfg.Environments > Unlimited { + if lcId == nil { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(acctId, trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(acctId, trx) + if err != nil { + return false, err + } + if lj.Action == store.LimitLimitAction { + return false, nil + } + } + } else { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, *lcId, trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, *lcId, trx) + if err != nil { + return false, err + } + if lj.Action == store.LimitLimitAction { + return false, nil + } + } + } + + if maxEnvironments > Unlimited { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err @@ -78,6 +102,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { } } } + return true, nil } From 93ecd833b207d36197ad8dfb43d9bffd1533e4aa Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 4 Jun 2024 16:43:38 -0400 Subject: [PATCH 034/117] updated rough-draft of CanCreateShare with limit classes support (#606) --- controller/limits/agent.go | 56 ++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 7f7c3e4f..89c8cbb7 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -102,28 +102,60 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { } } } - + return true, nil } -func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ sdk.ShareMode, _ sdk.BackendMode, trx *sqlx.Tx) (bool, error) { +func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, shareMode sdk.ShareMode, backendMode sdk.BackendMode, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err } - if empty, err := a.str.IsBandwidthLimitJournalEmpty(acctId, trx); err == nil && !empty { - alj, err := a.str.FindLatestBandwidthLimitJournal(acctId, trx) - if err != nil { - return false, err - } - if alj.Action == store.LimitLimitAction { - return false, nil - } - } else if err != nil { + + alcs, err := a.str.FindAppliedLimitClassesForAccount(acctId, trx) + if err != nil { return false, err } + maxShares := a.cfg.Shares + maxReservedShares := a.cfg.ReservedShares + maxUniqueNames := a.cfg.UniqueNames + var lcId *int + var points = -1 + for _, alc := range alcs { + if a.bandwidthClassPoints(alc) > points { + if alc.Shares >= maxShares || alc.ReservedShares >= maxReservedShares || alc.UniqueNames >= maxUniqueNames { + maxShares = alc.Shares + maxReservedShares = alc.ReservedShares + maxUniqueNames = alc.UniqueNames + lcId = &alc.Id + points = a.bandwidthClassPoints(alc) + } + } + } - if a.cfg.Shares > Unlimited || (reserved && a.cfg.ReservedShares > Unlimited) || (reserved && uniqueName && a.cfg.UniqueNames > Unlimited) { + if lcId == nil { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(acctId, trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(acctId, trx) + if err != nil { + return false, err + } + if lj.Action == store.LimitLimitAction { + return false, nil + } + } + } else { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, *lcId, trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, *lcId, trx) + if err != nil { + return false, err + } + if lj.Action == store.LimitLimitAction { + return false, nil + } + } + } + + if maxShares > Unlimited || (reserved && maxReservedShares > Unlimited) || (reserved && uniqueName && maxUniqueNames > Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err From 3258c3ee730b33d3e169b2f8107cfa2f0301a0d5 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 4 Jun 2024 16:50:08 -0400 Subject: [PATCH 035/117] roughed-in CanAccessShare supporting limit classes (#606) --- controller/limits/agent.go | 51 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 89c8cbb7..7efdf0bf 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -202,16 +202,55 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { if err != nil { return false, err } - if empty, err := a.str.IsBandwidthLimitJournalEmpty(shr.Id, trx); err == nil && !empty { - slj, err := a.str.FindLatestBandwidthLimitJournal(shr.Id, trx) + env, err := a.str.GetEnvironment(shr.EnvironmentId, trx) + if err != nil { + return false, err + } + if env.AccountId != nil { + alcs, err := a.str.FindAppliedLimitClassesForAccount(*env.AccountId, trx) if err != nil { return false, err } - if slj.Action == store.LimitLimitAction { - return false, nil + maxShares := a.cfg.Shares + maxReservedShares := a.cfg.ReservedShares + maxUniqueNames := a.cfg.UniqueNames + var lcId *int + var points = -1 + for _, alc := range alcs { + if a.bandwidthClassPoints(alc) > points { + if alc.Shares >= maxShares || alc.ReservedShares >= maxReservedShares || alc.UniqueNames >= maxUniqueNames { + maxShares = alc.Shares + maxReservedShares = alc.ReservedShares + maxUniqueNames = alc.UniqueNames + lcId = &alc.Id + points = a.bandwidthClassPoints(alc) + } + } } - } else if err != nil { - return false, err + + if lcId == nil { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(*env.AccountId, trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(*env.AccountId, trx) + if err != nil { + return false, err + } + if lj.Action == store.LimitLimitAction { + return false, nil + } + } + } else { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(*env.AccountId, *lcId, trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(*env.AccountId, *lcId, trx) + if err != nil { + return false, err + } + if lj.Action == store.LimitLimitAction { + return false, nil + } + } + } + } else { + return false, nil } } return true, nil From 0f32c5e8a35b56fc87fa945ac5838cc0f109df1e Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 11:45:14 -0400 Subject: [PATCH 036/117] basic global bandwidth enforcement testing tweaks (no clases) (#606) --- controller/limits/agent.go | 42 +++++++++++++------ .../{limitClasses.go => bandwidthClass.go} | 9 ++++ controller/store/bandwidthLimitJournal.go | 11 ++++- controller/store/limitClass.go | 9 ++-- ui/middleware.go | 3 -- 5 files changed, 51 insertions(+), 23 deletions(-) rename controller/limits/{limitClasses.go => bandwidthClass.go} (84%) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 7efdf0bf..07b2d1f0 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -337,14 +337,24 @@ func (a *Agent) enforce(u *metrics.Usage) error { if latest, err := a.str.FindLatestBandwidthLimitJournalForGlobal(int(u.AccountId), trx); err == nil { enforced = latest.Action == exceededLc.GetLimitAction() enforcedAt = latest.UpdatedAt + logrus.Debugf("limit '%v' already applied (enforced: %t)", exceededLc, enforced) + } else { + logrus.Errorf("error getting latest global bandwidth journal entry: %v", err) } + } else { + logrus.Debugf("no bandwidth limit journal entry for '%v'", exceededLc) } } else { if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(int(u.AccountId), exceededLc.GetLimitClassId(), trx); err == nil && !empty { if latest, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(int(u.AccountId), exceededLc.GetLimitClassId(), trx); err == nil { enforced = latest.Action == exceededLc.GetLimitAction() enforcedAt = latest.UpdatedAt + logrus.Debugf("limit '%v' already applied (enforced: %t)", exceededLc, enforced) + } else { + logrus.Errorf("error getting latest bandwidth limit journal entry for limit class '%d': %v", exceededLc.GetLimitClassId(), err) } + } else { + logrus.Debugf("no bandwidth limit journal entry for '%v'", exceededLc) } } @@ -423,7 +433,7 @@ func (a *Agent) relax() error { } var bwc store.BandwidthClass - if bwje.LimitClassId != nil { + if bwje.LimitClassId == nil { globalBwcs := newConfigBandwidthClasses(a.cfg.Bandwidth) if bwje.Action == store.WarningLimitAction { bwc = globalBwcs[0] @@ -455,27 +465,30 @@ func (a *Agent) relax() error { used := periodBw[bwc.GetPeriodMinutes()] if !a.limitExceeded(used.rx, used.tx, bwc) { if bwc.GetLimitAction() == store.LimitLimitAction { + logrus.Infof("relaxing limit '%v' for '%v'", bwc.String(), accounts[bwje.AccountId].Email) for _, action := range a.relaxActions { if err := action.HandleAccount(accounts[bwje.AccountId], used.rx, used.tx, bwc, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } } } else { - logrus.Infof("relaxing warning for '%v'", accounts[bwje.AccountId].Email) + logrus.Infof("relaxing warning '%v' for '%v'", bwc.String(), accounts[bwje.AccountId].Email) } - var lcId *int - if !bwc.IsGlobal() { - newLcId := 0 - newLcId = bwc.GetLimitClassId() - lcId = &newLcId - } - if err := a.str.DeleteBandwidthLimitJournalEntryForLimitClass(bwje.AccountId, lcId, trx); err == nil { - commit = true + if bwc.IsGlobal() { + if err := a.str.DeleteBandwidthLimitJournalEntryForGlobal(bwje.AccountId, trx); err == nil { + commit = true + } else { + logrus.Errorf("error deleting global bandwidth limit journal entry for '%v': %v", accounts[bwje.AccountId].Email, err) + } } else { - logrus.Errorf("error deleting bandwidth limit journal entry for '%v': %v", accounts[bwje.AccountId].Email, err) + if err := a.str.DeleteBandwidthLimitJournalEntryForLimitClass(bwje.AccountId, *bwje.LimitClassId, trx); err == nil { + commit = true + } else { + logrus.Errorf("error deleting bandwidth limit journal entry for '%v': %v", accounts[bwje.AccountId].Email, err) + } } } else { - logrus.Infof("account '%v' still over limit: %v", accounts[bwje.AccountId].Email, bwc) + logrus.Infof("account '%v' still over limit: '%v' with rx: %d, tx: %d", accounts[bwje.AccountId].Email, bwc, used.rx, used.tx) } } } else { @@ -536,6 +549,9 @@ func (a *Agent) isOverLimitClass(u *metrics.Usage, alcs []*store.LimitClass) (st selectedLcPoints = points rxBytes = period.rx txBytes = period.tx + logrus.Debugf("exceeded limit '%v' with rx: %d, tx: %d", bwc.String(), period.rx, period.tx) + } else { + logrus.Debugf("limit '%v' ok with rx: %d, tx: %d", bwc.String(), period.rx, period.tx) } } } @@ -570,7 +586,7 @@ func (a *Agent) limitExceeded(rx, tx int64, bwc store.BandwidthClass) bool { if bwc.GetRxBytes() != Unlimited && rx >= bwc.GetRxBytes() { return true } - if bwc.GetTxBytes() != Unlimited && bwc.GetRxBytes() != Unlimited && tx+rx >= bwc.GetTxBytes()+bwc.GetRxBytes() { + if bwc.GetTotalBytes() != Unlimited && tx+rx >= bwc.GetTotalBytes() { return true } return false diff --git a/controller/limits/limitClasses.go b/controller/limits/bandwidthClass.go similarity index 84% rename from controller/limits/limitClasses.go rename to controller/limits/bandwidthClass.go index a2d11c19..20127913 100644 --- a/controller/limits/limitClasses.go +++ b/controller/limits/bandwidthClass.go @@ -1,6 +1,8 @@ package limits import ( + "encoding/json" + "fmt" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/sdk/golang/sdk" ) @@ -61,3 +63,10 @@ func (bc *configBandwidthClass) GetTotalBytes() int64 { func (bc *configBandwidthClass) GetLimitAction() store.LimitAction { return bc.limitAction } + +func (bc *configBandwidthClass) String() string { + if out, err := json.Marshal(bc.bw); err == nil { + return fmt.Sprintf("Config", bc.periodInMinutes, string(out), bc.limitAction) + } + return "<>" +} diff --git a/controller/store/bandwidthLimitJournal.go b/controller/store/bandwidthLimitJournal.go index e3fb43c3..2fead8d8 100644 --- a/controller/store/bandwidthLimitJournal.go +++ b/controller/store/bandwidthLimitJournal.go @@ -52,7 +52,7 @@ func (str *Store) IsBandwidthLimitJournalEmptyForGlobal(acctId int, trx *sqlx.Tx func (str *Store) FindLatestBandwidthLimitJournalForGlobal(acctId int, trx *sqlx.Tx) (*BandwidthLimitJournalEntry, error) { j := &BandwidthLimitJournalEntry{} - if err := trx.QueryRowx("select * from bandwidth_limit_journal where account_id = $1 and limit_class_id is null order by id desc limit 1", acctId).Scan(&j); err != nil { + if err := trx.QueryRowx("select * from bandwidth_limit_journal where account_id = $1 and limit_class_id is null order by id desc limit 1", acctId).StructScan(j); err != nil { return nil, errors.Wrap(err, "error finding bandwidth_limit_journal by account_id for global") } return j, nil @@ -113,7 +113,14 @@ func (str *Store) DeleteBandwidthLimitJournal(acctId int, trx *sqlx.Tx) error { return nil } -func (str *Store) DeleteBandwidthLimitJournalEntryForLimitClass(acctId int, lcId *int, trx *sqlx.Tx) error { +func (str *Store) DeleteBandwidthLimitJournalEntryForGlobal(acctId int, trx *sqlx.Tx) error { + if _, err := trx.Exec("delete from bandwidth_limit_journal where account_id = $1 and limit_class_id is null", acctId); err != nil { + return errors.Wrapf(err, "error deleting from bandwidth_limit_journal for account_id = %d and limit_class_id is null", acctId) + } + return nil +} + +func (str *Store) DeleteBandwidthLimitJournalEntryForLimitClass(acctId int, lcId int, trx *sqlx.Tx) error { if _, err := trx.Exec("delete from bandwidth_limit_journal where account_id = $1 and limit_class_id = $2", acctId, lcId); err != nil { return errors.Wrapf(err, "error deleting from bandwidth_limit_journal for account_id = %d and limit_class_id = %d", acctId, lcId) } diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index f9a64e86..06a655c0 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -17,6 +17,7 @@ type BandwidthClass interface { GetTxBytes() int64 GetTotalBytes() int64 GetLimitAction() LimitAction + String() string } type LimitClass struct { @@ -71,12 +72,10 @@ func (lc LimitClass) GetLimitAction() LimitAction { } func (lc LimitClass) String() string { - out, err := json.MarshalIndent(&lc, "", " ") - if err != nil { - return "" - + if out, err := json.Marshal(&lc); err == nil { + return "LimitClass<" + string(out) + ">" } - return string(out) + return "<>" } var _ BandwidthClass = (*LimitClass)(nil) diff --git a/ui/middleware.go b/ui/middleware.go index b42b1ed0..4ca0a9f2 100644 --- a/ui/middleware.go +++ b/ui/middleware.go @@ -13,7 +13,6 @@ func Middleware(handler http.Handler, healthCheck func(w http.ResponseWriter, r logrus.Infof("building") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/v1") { - logrus.Debugf("directing '%v' to api handler", r.URL.Path) handler.ServeHTTP(w, r) return } @@ -22,8 +21,6 @@ func Middleware(handler http.Handler, healthCheck func(w http.ResponseWriter, r return } - logrus.Debugf("directing '%v' to static handler", r.URL.Path) - staticPath := "build" indexPath := "index.html" From e2a55393a51fd9d9907d8b7e9815083c8ac2ab84 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 12:56:27 -0400 Subject: [PATCH 037/117] formalize class selection to facilitate highest cascade when running limit actions (#606) --- controller/limits/agent.go | 5 +---- controller/limits/model.go | 7 +++++++ controller/store/limitClass.go | 25 ++++++++++++++++++++----- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 07b2d1f0..7276fdca 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -64,7 +64,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { maxEnvironments := a.cfg.Environments var lcId *int for _, alc := range alcs { - if alc.ShareMode == "" && alc.BackendMode == "" && alc.Environments > maxEnvironments { + if alc.ShareMode == nil && alc.BackendMode == nil && alc.Environments > maxEnvironments { maxEnvironments = alc.Environments lcId = &alc.Id } @@ -570,9 +570,6 @@ func (a *Agent) bandwidthClassPoints(bwc store.BandwidthClass) int { if bwc.GetLimitAction() == store.LimitLimitAction { points += 2 } - if bwc.GetShareMode() != "" { - points += 5 - } if bwc.GetBackendMode() != "" { points += 10 } diff --git a/controller/limits/model.go b/controller/limits/model.go index 7044c7d4..245556ac 100644 --- a/controller/limits/model.go +++ b/controller/limits/model.go @@ -3,8 +3,15 @@ package limits import ( "github.com/jmoiron/sqlx" "github.com/openziti/zrok/controller/store" + "github.com/openziti/zrok/sdk/golang/sdk" ) +type UserLimits struct { + resource store.ResourceCountClass + bandwidth store.BandwidthClass + scopes map[sdk.BackendMode]store.BandwidthClass +} + type AccountAction interface { HandleAccount(a *store.Account, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error } diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 06a655c0..5204d54e 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -7,10 +7,19 @@ import ( "github.com/pkg/errors" ) +type ResourceCountClass interface { + IsGlobal() bool + GetLimitClassId() int + GetEnvironments() int + GetShares() int + GetReservedShares() int + GetUniqueNames() int + String() +} + type BandwidthClass interface { IsGlobal() bool GetLimitClassId() int - GetShareMode() sdk.ShareMode GetBackendMode() sdk.BackendMode GetPeriodMinutes() int GetRxBytes() int64 @@ -22,8 +31,8 @@ type BandwidthClass interface { type LimitClass struct { Model - ShareMode sdk.ShareMode - BackendMode sdk.BackendMode + ShareMode *sdk.ShareMode + BackendMode *sdk.BackendMode Environments int Shares int ReservedShares int @@ -44,11 +53,17 @@ func (lc LimitClass) GetLimitClassId() int { } func (lc LimitClass) GetShareMode() sdk.ShareMode { - return lc.ShareMode + if lc.ShareMode == nil { + return "" + } + return *lc.ShareMode } func (lc LimitClass) GetBackendMode() sdk.BackendMode { - return lc.BackendMode + if lc.BackendMode == nil { + return "" + } + return *lc.BackendMode } func (lc LimitClass) GetPeriodMinutes() int { From 7b8c9483e7e27d3aed513793e46758bde606bd20 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 14:55:39 -0400 Subject: [PATCH 038/117] fix limitclass creation in test (#606) --- controller/limits/model.go | 7 ------- controller/limits/userLimits.go | 17 +++++++++++++++++ controller/store/bandwidthLimitJournal_test.go | 11 +++++++---- 3 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 controller/limits/userLimits.go diff --git a/controller/limits/model.go b/controller/limits/model.go index 245556ac..7044c7d4 100644 --- a/controller/limits/model.go +++ b/controller/limits/model.go @@ -3,15 +3,8 @@ package limits import ( "github.com/jmoiron/sqlx" "github.com/openziti/zrok/controller/store" - "github.com/openziti/zrok/sdk/golang/sdk" ) -type UserLimits struct { - resource store.ResourceCountClass - bandwidth store.BandwidthClass - scopes map[sdk.BackendMode]store.BandwidthClass -} - type AccountAction interface { HandleAccount(a *store.Account, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error } diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go new file mode 100644 index 00000000..6ce033b7 --- /dev/null +++ b/controller/limits/userLimits.go @@ -0,0 +1,17 @@ +package limits + +import ( + "github.com/jmoiron/sqlx" + "github.com/openziti/zrok/controller/store" + "github.com/openziti/zrok/sdk/golang/sdk" +) + +type userLimits struct { + resource store.ResourceCountClass + bandwidth store.BandwidthClass + scopes map[sdk.BackendMode]store.BandwidthClass +} + +func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { + return nil, nil +} diff --git a/controller/store/bandwidthLimitJournal_test.go b/controller/store/bandwidthLimitJournal_test.go index 9354fc4b..da8fe784 100644 --- a/controller/store/bandwidthLimitJournal_test.go +++ b/controller/store/bandwidthLimitJournal_test.go @@ -37,15 +37,18 @@ func TestBandwidthLimitJournal(t *testing.T) { assert.Equal(t, int64(1024), latestJe.RxBytes) assert.Equal(t, int64(2048), latestJe.TxBytes) - lcId, err := str.CreateLimitClass(&LimitClass{ - ShareMode: sdk.PrivateShareMode, - BackendMode: sdk.VpnBackendMode, + lc := &LimitClass{ + ShareMode: new(sdk.ShareMode), + BackendMode: new(sdk.BackendMode), PeriodMinutes: 60, RxBytes: 4096, TxBytes: 8192, TotalBytes: 10240, LimitAction: LimitLimitAction, - }, trx) + } + *lc.ShareMode = sdk.PrivateShareMode + *lc.BackendMode = sdk.ProxyBackendMode + lcId, err := str.CreateLimitClass(lc, trx) assert.NoError(t, err) _, err = str.CreateBandwidthLimitJournalEntry(&BandwidthLimitJournalEntry{AccountId: acctId, LimitClassId: &lcId, Action: LimitLimitAction, RxBytes: 10240, TxBytes: 20480}, trx) From a9dff531fce9361b197cf25b0326165c92bf253f Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 15:20:33 -0400 Subject: [PATCH 039/117] refactoring limits selection into a new userLimits structure (#606) --- controller/limits/agent.go | 10 ++--- controller/limits/config.go | 27 ++++++------ controller/limits/resourceCountClass.go | 42 ++++++++++++++++++ controller/limits/userLimits.go | 4 +- controller/limits/warningAction.go | 12 +++--- controller/store/limitClass.go | 57 ++++++++++++++++++++++--- 6 files changed, 122 insertions(+), 30 deletions(-) create mode 100644 controller/limits/resourceCountClass.go diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 7276fdca..29dc828d 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -92,7 +92,7 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { } } - if maxEnvironments > Unlimited { + if maxEnvironments > store.Unlimited { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err @@ -155,7 +155,7 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, sha } } - if maxShares > Unlimited || (reserved && maxReservedShares > Unlimited) || (reserved && uniqueName && maxUniqueNames > Unlimited) { + if maxShares > store.Unlimited || (reserved && maxReservedShares > store.Unlimited) || (reserved && uniqueName && maxUniqueNames > store.Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err @@ -577,13 +577,13 @@ func (a *Agent) bandwidthClassPoints(bwc store.BandwidthClass) int { } func (a *Agent) limitExceeded(rx, tx int64, bwc store.BandwidthClass) bool { - if bwc.GetTxBytes() != Unlimited && tx >= bwc.GetTxBytes() { + if bwc.GetTxBytes() != store.Unlimited && tx >= bwc.GetTxBytes() { return true } - if bwc.GetRxBytes() != Unlimited && rx >= bwc.GetRxBytes() { + if bwc.GetRxBytes() != store.Unlimited && rx >= bwc.GetRxBytes() { return true } - if bwc.GetTotalBytes() != Unlimited && tx+rx >= bwc.GetTotalBytes() { + if bwc.GetTotalBytes() != store.Unlimited && tx+rx >= bwc.GetTotalBytes() { return true } return false diff --git a/controller/limits/config.go b/controller/limits/config.go index bd2c28d2..2bc0c8a8 100644 --- a/controller/limits/config.go +++ b/controller/limits/config.go @@ -1,8 +1,9 @@ package limits -import "time" - -const Unlimited = -1 +import ( + "github.com/openziti/zrok/controller/store" + "time" +) type Config struct { Environments int @@ -30,24 +31,24 @@ func DefaultBandwidthPerPeriod() *BandwidthPerPeriod { return &BandwidthPerPeriod{ Period: 24 * time.Hour, Warning: &Bandwidth{ - Rx: Unlimited, - Tx: Unlimited, - Total: Unlimited, + Rx: store.Unlimited, + Tx: store.Unlimited, + Total: store.Unlimited, }, Limit: &Bandwidth{ - Rx: Unlimited, - Tx: Unlimited, - Total: Unlimited, + Rx: store.Unlimited, + Tx: store.Unlimited, + Total: store.Unlimited, }, } } func DefaultConfig() *Config { return &Config{ - Environments: Unlimited, - Shares: Unlimited, - ReservedShares: Unlimited, - UniqueNames: Unlimited, + Environments: store.Unlimited, + Shares: store.Unlimited, + ReservedShares: store.Unlimited, + UniqueNames: store.Unlimited, Bandwidth: DefaultBandwidthPerPeriod(), Enforcing: false, Cycle: 15 * time.Minute, diff --git a/controller/limits/resourceCountClass.go b/controller/limits/resourceCountClass.go new file mode 100644 index 00000000..ca9a6a91 --- /dev/null +++ b/controller/limits/resourceCountClass.go @@ -0,0 +1,42 @@ +package limits + +import ( + "fmt" + "github.com/openziti/zrok/controller/store" +) + +type configResourceCountClass struct { + cfg *Config +} + +func newConfigResourceCountClass(cfg *Config) store.ResourceCountClass { + return &configResourceCountClass{cfg} +} + +func (rcc *configResourceCountClass) IsGlobal() bool { + return true +} + +func (rcc *configResourceCountClass) GetLimitClassId() int { + return -1 +} + +func (rcc *configResourceCountClass) GetEnvironments() int { + return rcc.cfg.Environments +} + +func (rcc *configResourceCountClass) GetShares() int { + return rcc.cfg.Shares +} + +func (rcc *configResourceCountClass) GetReservedShares() int { + return rcc.cfg.ReservedShares +} + +func (rcc *configResourceCountClass) GetUniqueNames() int { + return rcc.cfg.UniqueNames +} + +func (rcc *configResourceCountClass) String() string { + return fmt.Sprintf("Config", rcc.cfg.Environments, rcc.cfg.Shares, rcc.cfg.ReservedShares, rcc.cfg.UniqueNames) +} diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index 6ce033b7..177f15a1 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -13,5 +13,7 @@ type userLimits struct { } func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { - return nil, nil + _ = newConfigBandwidthClasses(a.cfg.Bandwidth) + userLimits := &userLimits{} + return userLimits, nil } diff --git a/controller/limits/warningAction.go b/controller/limits/warningAction.go index 8e1841fd..21b6611b 100644 --- a/controller/limits/warningAction.go +++ b/controller/limits/warningAction.go @@ -23,16 +23,16 @@ func (a *warningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int6 logrus.Infof("warning '%v'", acct.Email) if a.cfg != nil { - rxLimit := "(unlimited bytes)" - if limit.GetRxBytes() != Unlimited { + rxLimit := "(store.Unlimited bytes)" + if limit.GetRxBytes() != store.Unlimited { rxLimit = util.BytesToSize(limit.GetRxBytes()) } - txLimit := "(unlimited bytes)" - if limit.GetTxBytes() != Unlimited { + txLimit := "(store.Unlimited bytes)" + if limit.GetTxBytes() != store.Unlimited { txLimit = util.BytesToSize(limit.GetTxBytes()) } - totalLimit := "(unlimited bytes)" - if limit.GetTotalBytes() != Unlimited { + totalLimit := "(store.Unlimited bytes)" + if limit.GetTotalBytes() != store.Unlimited { totalLimit = util.BytesToSize(limit.GetTotalBytes()) } diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 5204d54e..d0be95ae 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -1,12 +1,14 @@ package store import ( - "encoding/json" + "fmt" "github.com/jmoiron/sqlx" "github.com/openziti/zrok/sdk/golang/sdk" "github.com/pkg/errors" ) +const Unlimited = -1 + type ResourceCountClass interface { IsGlobal() bool GetLimitClassId() int @@ -14,7 +16,7 @@ type ResourceCountClass interface { GetShares() int GetReservedShares() int GetUniqueNames() int - String() + String() string } type BandwidthClass interface { @@ -52,6 +54,22 @@ func (lc LimitClass) GetLimitClassId() int { return lc.Id } +func (lc LimitClass) GetEnvironments() int { + return lc.Environments +} + +func (lc LimitClass) GetShares() int { + return lc.Shares +} + +func (lc LimitClass) GetReservedShares() int { + return lc.ReservedShares +} + +func (lc LimitClass) GetUniqueNames() int { + return lc.UniqueNames +} + func (lc LimitClass) GetShareMode() sdk.ShareMode { if lc.ShareMode == nil { return "" @@ -87,10 +105,39 @@ func (lc LimitClass) GetLimitAction() LimitAction { } func (lc LimitClass) String() string { - if out, err := json.Marshal(&lc); err == nil { - return "LimitClass<" + string(out) + ">" + out := fmt.Sprintf("LimitClass<%d", lc.Id) + if lc.ShareMode != nil { + out += fmt.Sprintf(", shareMode: '%s'", *lc.ShareMode) } - return "<>" + if lc.BackendMode != nil { + out += fmt.Sprintf(", backendMode: '%s'", *lc.BackendMode) + } + if lc.Environments > Unlimited { + out += fmt.Sprintf(", environments: %d", lc.Environments) + } + if lc.Shares > Unlimited { + out += fmt.Sprintf(", shares: %d", lc.Shares) + } + if lc.ReservedShares > Unlimited { + out += fmt.Sprintf(", reservedShares: %d", lc.ReservedShares) + } + if lc.UniqueNames > Unlimited { + out += fmt.Sprintf(", uniqueNames: %d", lc.UniqueNames) + } + if lc.RxBytes > Unlimited || lc.TxBytes > Unlimited || lc.TotalBytes > Unlimited { + out += fmt.Sprintf(", periodMinutes: %d", lc.PeriodMinutes) + } + if lc.RxBytes > Unlimited { + out += fmt.Sprintf(", rxBytes: %d", lc.RxBytes) + } + if lc.TxBytes > Unlimited { + out += fmt.Sprintf(", txBytes: %d", lc.TxBytes) + } + if lc.TotalBytes > Unlimited { + out += fmt.Sprintf(", totalBytes: %d", lc.TotalBytes) + } + out += fmt.Sprintf(", limitAction: '%v'>", lc.LimitAction) + return out } var _ BandwidthClass = (*LimitClass)(nil) From 56252b6e0adb3c09341335606f87e286541277ac Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 15:45:36 -0400 Subject: [PATCH 040/117] more user limits selection refinements (#606) --- controller/limits/userLimits.go | 80 +++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index 177f15a1..97513c20 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -4,16 +4,90 @@ import ( "github.com/jmoiron/sqlx" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) type userLimits struct { resource store.ResourceCountClass - bandwidth store.BandwidthClass + bandwidth []store.BandwidthClass scopes map[sdk.BackendMode]store.BandwidthClass } func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { - _ = newConfigBandwidthClasses(a.cfg.Bandwidth) - userLimits := &userLimits{} + resource := newConfigResourceCountClass(a.cfg) + cfgBwcs := newConfigBandwidthClasses(a.cfg.Bandwidth) + bwWarning := cfgBwcs[0] + bwLimit := cfgBwcs[1] + scopes := map[sdk.BackendMode]store.BandwidthClass{} + + alcs, err := a.str.FindAppliedLimitClassesForAccount(acctId, trx) + if err != nil { + return nil, errors.Wrapf(err, "error finding applied limit classes for account '%d'", acctId) + } + for _, alc := range alcs { + if a.isResourceCountClass(alc) { + resource = alc + } else if a.isUnscopedBandwidthClass(alc) { + if alc.LimitAction == store.WarningLimitAction { + bwWarning = alc + } else { + bwLimit = alc + } + } else if a.isScopedBandwidthClass(alc) { + scopes[*alc.BackendMode] = alc + } else { + logrus.Warnf("unknown type of limit class '%v'", alc) + } + } + + userLimits := &userLimits{ + resource: resource, + bandwidth: []store.BandwidthClass{bwWarning, bwLimit}, + scopes: scopes, + } + return userLimits, nil } + +func (a *Agent) isResourceCountClass(alc *store.LimitClass) bool { + if alc.BackendMode != nil { + return false + } + if alc.Environments == store.Unlimited && alc.Shares == store.Unlimited && alc.ReservedShares == store.Unlimited && alc.UniqueNames == store.Unlimited { + return false + } + return true +} + +func (a *Agent) isUnscopedBandwidthClass(alc *store.LimitClass) bool { + if alc.BackendMode != nil { + return false + } + if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited { + return false + } + if alc.PeriodMinutes < 1 { + return false + } + if alc.RxBytes == store.Unlimited && alc.TxBytes == store.Unlimited && alc.TotalBytes == store.Unlimited { + return false + } + return true +} + +func (a *Agent) isScopedBandwidthClass(alc *store.LimitClass) bool { + if alc.BackendMode == nil { + return false + } + if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited { + return false + } + if alc.PeriodMinutes < 1 { + return false + } + if alc.RxBytes == store.Unlimited && alc.TxBytes == store.Unlimited && alc.TotalBytes == store.Unlimited { + return false + } + return true +} From b511eeee17ab1f23c951a3544fd288291209c360 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 16:54:05 -0400 Subject: [PATCH 041/117] use the new userLimits structure to find the right bandwidth class (#606) --- controller/limits/agent.go | 59 +++++++++++++++----------------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 29dc828d..16d24302 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -313,17 +313,17 @@ func (a *Agent) enforce(u *metrics.Usage) error { return nil } - shr, err := a.str.FindShareWithTokenEvenIfDeleted(u.ShareToken, trx) - if err != nil { - return err - } - logrus.Debugf("share: '%v', shareMode: '%v', backendMode: '%v'", shr.Token, shr.ShareMode, shr.BackendMode) + //shr, err := a.str.FindShareWithTokenEvenIfDeleted(u.ShareToken, trx) + //if err != nil { + // return err + //} - alcs, err := a.str.FindAppliedLimitClassesForAccount(int(u.AccountId), trx) + ul, err := a.getUserLimits(int(u.AccountId), trx) if err != nil { return err } - exceededLc, rxBytes, txBytes, err := a.isOverLimitClass(u, alcs) + + exceededLc, rxBytes, txBytes, err := a.isOverLimitClass(u, ul.bandwidth) if err != nil { return errors.Wrap(err, "error checking limit classes") } @@ -504,22 +504,17 @@ func (a *Agent) relax() error { return nil } -func (a *Agent) isOverLimitClass(u *metrics.Usage, alcs []*store.LimitClass) (store.BandwidthClass, int64, int64, error) { +func (a *Agent) isOverLimitClass(u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { periodBw := make(map[int]struct { rx int64 tx int64 }) - var allBwcs []store.BandwidthClass - for _, alc := range alcs { - allBwcs = append(allBwcs, alc) - } - for _, globBwc := range newConfigBandwidthClasses(a.cfg.Bandwidth) { - allBwcs = append(allBwcs, globBwc) - } + var selectedLc store.BandwidthClass + var rxBytes int64 + var txBytes int64 - // find period data for each class - for _, bwc := range allBwcs { + for _, bwc := range bwcs { if _, found := periodBw[bwc.GetPeriodMinutes()]; !found { rx, tx, err := a.ifx.totalRxTxForAccount(u.AccountId, time.Minute*time.Duration(bwc.GetPeriodMinutes())) if err != nil { @@ -533,27 +528,19 @@ func (a *Agent) isOverLimitClass(u *metrics.Usage, alcs []*store.LimitClass) (st tx: tx, } } + period := periodBw[bwc.GetPeriodMinutes()] + + if a.limitExceeded(period.rx, period.tx, bwc) { + selectedLc = bwc + rxBytes = period.rx + txBytes = period.tx + } else { + logrus.Debugf("limit ok '%v' with rx: %d, tx: %d, total: %d", bwc, period.rx, period.tx, period.rx+period.tx) + } } - // find the highest, most specific limit class that has been exceeded - var selectedLc store.BandwidthClass - selectedLcPoints := -1 - var rxBytes int64 - var txBytes int64 - for _, bwc := range allBwcs { - points := a.bandwidthClassPoints(bwc) - if points >= selectedLcPoints { - period := periodBw[bwc.GetPeriodMinutes()] - if a.limitExceeded(period.rx, period.tx, bwc) { - selectedLc = bwc - selectedLcPoints = points - rxBytes = period.rx - txBytes = period.tx - logrus.Debugf("exceeded limit '%v' with rx: %d, tx: %d", bwc.String(), period.rx, period.tx) - } else { - logrus.Debugf("limit '%v' ok with rx: %d, tx: %d", bwc.String(), period.rx, period.tx) - } - } + if selectedLc != nil { + logrus.Infof("exceeded limit '%v' with rx: %d, tx: %d, total: %d", selectedLc, rxBytes, txBytes, rxBytes+txBytes) } return selectedLc, rxBytes, txBytes, nil From 83e9e84dad062852a97266ca121871f353381795 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 17:03:24 -0400 Subject: [PATCH 042/117] mix in scoped bandwidth clases (#606) --- controller/limits/agent.go | 10 +++++----- controller/limits/userLimits.go | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 16d24302..3a3be465 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -313,17 +313,17 @@ func (a *Agent) enforce(u *metrics.Usage) error { return nil } - //shr, err := a.str.FindShareWithTokenEvenIfDeleted(u.ShareToken, trx) - //if err != nil { - // return err - //} + shr, err := a.str.FindShareWithTokenEvenIfDeleted(u.ShareToken, trx) + if err != nil { + return err + } ul, err := a.getUserLimits(int(u.AccountId), trx) if err != nil { return err } - exceededLc, rxBytes, txBytes, err := a.isOverLimitClass(u, ul.bandwidth) + exceededLc, rxBytes, txBytes, err := a.isOverLimitClass(u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) if err != nil { return errors.Wrap(err, "error checking limit classes") } diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index 97513c20..bc654160 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -14,6 +14,18 @@ type userLimits struct { scopes map[sdk.BackendMode]store.BandwidthClass } +func (ul *userLimits) toBandwidthArray(backendMode sdk.BackendMode) []store.BandwidthClass { + if scopedBwc, found := ul.scopes[backendMode]; found { + out := make([]store.BandwidthClass, 0) + for _, bwc := range ul.bandwidth { + out = append(out, bwc) + } + out = append(out, scopedBwc) + return out + } + return ul.bandwidth +} + func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { resource := newConfigResourceCountClass(a.cfg) cfgBwcs := newConfigBandwidthClasses(a.cfg.Bandwidth) From cb4afd4a0fb3107d633bd07f71e8bd5037138e59 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 5 Jun 2024 21:11:23 -0400 Subject: [PATCH 043/117] lint (#606) --- controller/limits/agent.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 3a3be465..93b584ec 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -323,7 +323,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { return err } - exceededLc, rxBytes, txBytes, err := a.isOverLimitClass(u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) + exceededLc, rxBytes, txBytes, err := a.hasExceededBandwidthLimit(u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) if err != nil { return errors.Wrap(err, "error checking limit classes") } @@ -369,9 +369,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { lcId := exceededLc.GetLimitClassId() je.LimitClassId = &lcId } - _, err := a.str.CreateBandwidthLimitJournalEntry(je, trx) - - if err != nil { + if _, err := a.str.CreateBandwidthLimitJournalEntry(je, trx); err != nil { return err } acct, err := a.str.GetAccount(int(u.AccountId), trx) @@ -504,7 +502,7 @@ func (a *Agent) relax() error { return nil } -func (a *Agent) isOverLimitClass(u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { +func (a *Agent) hasExceededBandwidthLimit(u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { periodBw := make(map[int]struct { rx int64 tx int64 From bee5356e3ccce89a072eef875ad31c4645fea400 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 6 Jun 2024 13:49:36 -0400 Subject: [PATCH 044/117] better support for scoped/unscoped bandwidth limit coexistence (#606) --- controller/limits/agent.go | 15 +++++++++++---- controller/limits/bandwidthClass.go | 19 +++++++++++++++---- controller/limits/limitAction.go | 14 ++++++++++---- controller/limits/model.go | 10 +--------- controller/limits/relaxAction.go | 21 ++++++++++++--------- controller/limits/userLimits.go | 13 +++++++++++++ controller/limits/warningAction.go | 18 +++++++++--------- controller/store/limitClass.go | 19 +++++++++++++------ 8 files changed, 84 insertions(+), 45 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 93b584ec..6ea57ff2 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -379,14 +379,14 @@ func (a *Agent) enforce(u *metrics.Usage) error { switch exceededLc.GetLimitAction() { case store.LimitLimitAction: for _, limitAction := range a.limitActions { - if err := limitAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, trx); err != nil { + if err := limitAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, ul, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(limitAction).String()) } } case store.WarningLimitAction: for _, warningAction := range a.warningActions { - if err := warningAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, trx); err != nil { + if err := warningAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, ul, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(warningAction).String()) } } @@ -420,11 +420,18 @@ func (a *Agent) relax() error { }) accounts := make(map[int]*store.Account) + uls := make(map[int]*userLimits) for _, bwje := range bwjes { if _, found := accounts[bwje.AccountId]; !found { if acct, err := a.str.GetAccount(bwje.AccountId, trx); err == nil { accounts[bwje.AccountId] = acct + ul, err := a.getUserLimits(acct.Id, trx) + if err != nil { + return errors.Wrapf(err, "error getting user limits for '%v'", acct.Email) + } + uls[bwje.AccountId] = ul + } else { return err } @@ -465,7 +472,7 @@ func (a *Agent) relax() error { if bwc.GetLimitAction() == store.LimitLimitAction { logrus.Infof("relaxing limit '%v' for '%v'", bwc.String(), accounts[bwje.AccountId].Email) for _, action := range a.relaxActions { - if err := action.HandleAccount(accounts[bwje.AccountId], used.rx, used.tx, bwc, trx); err != nil { + if err := action.HandleAccount(accounts[bwje.AccountId], used.rx, used.tx, bwc, uls[bwje.AccountId], trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(action).String()) } } @@ -486,7 +493,7 @@ func (a *Agent) relax() error { } } } else { - logrus.Infof("account '%v' still over limit: '%v' with rx: %d, tx: %d", accounts[bwje.AccountId].Email, bwc, used.rx, used.tx) + logrus.Infof("account '%v' still over limit: '%v' with rx: %d, tx: %d, total: %d", accounts[bwje.AccountId].Email, bwc, used.rx, used.tx, used.rx+used.tx) } } } else { diff --git a/controller/limits/bandwidthClass.go b/controller/limits/bandwidthClass.go index 20127913..fca22469 100644 --- a/controller/limits/bandwidthClass.go +++ b/controller/limits/bandwidthClass.go @@ -1,7 +1,6 @@ package limits import ( - "encoding/json" "fmt" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/sdk/golang/sdk" @@ -32,6 +31,10 @@ func (bc *configBandwidthClass) IsGlobal() bool { return true } +func (bc *configBandwidthClass) IsScoped() bool { + return false +} + func (bc *configBandwidthClass) GetLimitClassId() int { return -1 } @@ -65,8 +68,16 @@ func (bc *configBandwidthClass) GetLimitAction() store.LimitAction { } func (bc *configBandwidthClass) String() string { - if out, err := json.Marshal(bc.bw); err == nil { - return fmt.Sprintf("Config", bc.periodInMinutes, string(out), bc.limitAction) + out := fmt.Sprintf("ConfigClass store.Unlimited { + out += fmt.Sprintf(", rxBytes: %d", bc.bw.Rx) } - return "<>" + if bc.bw.Tx > store.Unlimited { + out += fmt.Sprintf(", txBytes: %d", bc.bw.Tx) + } + if bc.bw.Total > store.Unlimited { + out += fmt.Sprintf(", totalBytes: %d", bc.bw.Total) + } + out += fmt.Sprintf(", limitAction: %s>", bc.limitAction) + return out } diff --git a/controller/limits/limitAction.go b/controller/limits/limitAction.go index 6b857ee0..0338ad97 100644 --- a/controller/limits/limitAction.go +++ b/controller/limits/limitAction.go @@ -4,6 +4,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/controller/zrokEdgeSdk" + "github.com/openziti/zrok/sdk/golang/sdk" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -17,7 +18,7 @@ func newLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *limitAction { return &limitAction{str, zCfg} } -func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, _ store.BandwidthClass, trx *sqlx.Tx) error { +func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.BandwidthClass, ul *userLimits, trx *sqlx.Tx) error { logrus.Infof("limiting '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) @@ -30,6 +31,7 @@ func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, _ store.Ban return err } + ignoreBackends := ul.ignoreBackends(bwc) for _, env := range envs { shrs, err := a.str.FindSharesForEnvironment(env.Id, trx) if err != nil { @@ -37,10 +39,14 @@ func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, _ store.Ban } for _, shr := range shrs { - if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil { - return errors.Wrapf(err, "error deleting dial service policy for '%v'", shr.Token) + if _, ignore := ignoreBackends[sdk.BackendMode(shr.BackendMode)]; !ignore { + if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil { + return errors.Wrapf(err, "error deleting dial service policy for '%v'", shr.Token) + } + logrus.Infof("removed dial service policy for share '%v' of environment '%v'", shr.Token, env.ZId) + } else { + logrus.Debugf("ignoring share '%v' for '%v' with backend mode '%v'", shr.Token, acct.Email, shr.BackendMode) } - logrus.Infof("removed dial service policy for share '%v' of environment '%v'", shr.Token, env.ZId) } } diff --git a/controller/limits/model.go b/controller/limits/model.go index 7044c7d4..cf70e752 100644 --- a/controller/limits/model.go +++ b/controller/limits/model.go @@ -6,13 +6,5 @@ import ( ) type AccountAction interface { - HandleAccount(a *store.Account, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error -} - -type EnvironmentAction interface { - HandleEnvironment(e *store.Environment, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error -} - -type ShareAction interface { - HandleShare(s *store.Share, rxBytes, txBytes int64, limit store.BandwidthClass, trx *sqlx.Tx) error + HandleAccount(a *store.Account, rxBytes, txBytes int64, bwc store.BandwidthClass, ul *userLimits, trx *sqlx.Tx) error } diff --git a/controller/limits/relaxAction.go b/controller/limits/relaxAction.go index 7700e3e2..f0df0441 100644 --- a/controller/limits/relaxAction.go +++ b/controller/limits/relaxAction.go @@ -19,7 +19,7 @@ func newRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *relaxAction { return &relaxAction{str, zCfg} } -func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, _ store.BandwidthClass, trx *sqlx.Tx) error { +func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.BandwidthClass, _ *userLimits, trx *sqlx.Tx) error { logrus.Infof("relaxing '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) @@ -39,14 +39,17 @@ func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, _ store.Ban } for _, shr := range shrs { - switch shr.ShareMode { - case string(sdk.PublicShareMode): - if err := relaxPublicShare(a.str, edge, shr, trx); err != nil { - return errors.Wrap(err, "error relaxing public share") - } - case string(sdk.PrivateShareMode): - if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil { - return errors.Wrap(err, "error relaxing private share") + // TODO: when relaxing unscoped classes; need to not relax other scoped limits + if !bwc.IsScoped() || bwc.GetBackendMode() == sdk.BackendMode(shr.BackendMode) { + switch shr.ShareMode { + case string(sdk.PublicShareMode): + if err := relaxPublicShare(a.str, edge, shr, trx); err != nil { + return errors.Wrap(err, "error relaxing public share") + } + case string(sdk.PrivateShareMode): + if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil { + return errors.Wrap(err, "error relaxing private share") + } } } } diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index bc654160..bf40b2ca 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -26,6 +26,19 @@ func (ul *userLimits) toBandwidthArray(backendMode sdk.BackendMode) []store.Band return ul.bandwidth } +func (ul *userLimits) ignoreBackends(bwc store.BandwidthClass) map[sdk.BackendMode]bool { + if bwc.IsScoped() { + ignoreBackends := make(map[sdk.BackendMode]bool) + for backendMode := range ul.scopes { + if backendMode != bwc.GetBackendMode() { + ignoreBackends[backendMode] = true + } + } + return ignoreBackends + } + return nil +} + func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { resource := newConfigResourceCountClass(a.cfg) cfgBwcs := newConfigBandwidthClasses(a.cfg.Bandwidth) diff --git a/controller/limits/warningAction.go b/controller/limits/warningAction.go index 21b6611b..4e6f7858 100644 --- a/controller/limits/warningAction.go +++ b/controller/limits/warningAction.go @@ -19,27 +19,27 @@ func newWarningAction(cfg *emailUi.Config, str *store.Store) *warningAction { return &warningAction{str, cfg} } -func (a *warningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, limit store.BandwidthClass, _ *sqlx.Tx) error { +func (a *warningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, bwc store.BandwidthClass, _ *userLimits, _ *sqlx.Tx) error { logrus.Infof("warning '%v'", acct.Email) if a.cfg != nil { rxLimit := "(store.Unlimited bytes)" - if limit.GetRxBytes() != store.Unlimited { - rxLimit = util.BytesToSize(limit.GetRxBytes()) + if bwc.GetRxBytes() != store.Unlimited { + rxLimit = util.BytesToSize(bwc.GetRxBytes()) } txLimit := "(store.Unlimited bytes)" - if limit.GetTxBytes() != store.Unlimited { - txLimit = util.BytesToSize(limit.GetTxBytes()) + if bwc.GetTxBytes() != store.Unlimited { + txLimit = util.BytesToSize(bwc.GetTxBytes()) } totalLimit := "(store.Unlimited bytes)" - if limit.GetTotalBytes() != store.Unlimited { - totalLimit = util.BytesToSize(limit.GetTotalBytes()) + if bwc.GetTotalBytes() != store.Unlimited { + totalLimit = util.BytesToSize(bwc.GetTotalBytes()) } detail := newDetailMessage() detail = detail.append("Your account has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes)) - detail = detail.append("This zrok instance only allows an account to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, time.Duration(limit.GetPeriodMinutes())*time.Minute) - detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit)", time.Duration(limit.GetPeriodMinutes())*time.Minute) + detail = detail.append("This zrok instance only allows an account to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, time.Duration(bwc.GetPeriodMinutes())*time.Minute) + detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit)", time.Duration(bwc.GetPeriodMinutes())*time.Minute) if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil { return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email) diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index d0be95ae..ef0991f8 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -9,26 +9,29 @@ import ( const Unlimited = -1 -type ResourceCountClass interface { +type BaseLimitClass interface { IsGlobal() bool GetLimitClassId() int + String() string +} + +type ResourceCountClass interface { + BaseLimitClass GetEnvironments() int GetShares() int GetReservedShares() int GetUniqueNames() int - String() string } type BandwidthClass interface { - IsGlobal() bool - GetLimitClassId() int + BaseLimitClass + IsScoped() bool GetBackendMode() sdk.BackendMode GetPeriodMinutes() int GetRxBytes() int64 GetTxBytes() int64 GetTotalBytes() int64 GetLimitAction() LimitAction - String() string } type LimitClass struct { @@ -50,6 +53,10 @@ func (lc LimitClass) IsGlobal() bool { return false } +func (lc LimitClass) IsScoped() bool { + return lc.BackendMode != nil +} + func (lc LimitClass) GetLimitClassId() int { return lc.Id } @@ -105,7 +112,7 @@ func (lc LimitClass) GetLimitAction() LimitAction { } func (lc LimitClass) String() string { - out := fmt.Sprintf("LimitClass<%d", lc.Id) + out := fmt.Sprintf("LimitClass Date: Thu, 6 Jun 2024 14:00:49 -0400 Subject: [PATCH 045/117] simpified CanCreateEnvironment that just uses the resource class from the user limits (#606) --- controller/limits/agent.go | 34 ++-------------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 6ea57ff2..b8a817e0 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -57,42 +57,12 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { return false, err } - alcs, err := a.str.FindAppliedLimitClassesForAccount(acctId, trx) + ul, err := a.getUserLimits(acctId, trx) if err != nil { return false, err } - maxEnvironments := a.cfg.Environments - var lcId *int - for _, alc := range alcs { - if alc.ShareMode == nil && alc.BackendMode == nil && alc.Environments > maxEnvironments { - maxEnvironments = alc.Environments - lcId = &alc.Id - } - } - if lcId == nil { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(acctId, trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(acctId, trx) - if err != nil { - return false, err - } - if lj.Action == store.LimitLimitAction { - return false, nil - } - } - } else { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, *lcId, trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, *lcId, trx) - if err != nil { - return false, err - } - if lj.Action == store.LimitLimitAction { - return false, nil - } - } - } - - if maxEnvironments > store.Unlimited { + if ul.resource.GetEnvironments() > store.Unlimited { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err From 26acd4f5a6fc1fda0b57d2d830341ac68bc767fe Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 10:21:26 -0400 Subject: [PATCH 046/117] cleaner userLimits implementation (#606) --- controller/limits/agent.go | 58 +++++++++----------------------------- 1 file changed, 13 insertions(+), 45 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index b8a817e0..8f9ec89a 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -76,34 +76,18 @@ func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) { return true, nil } -func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, shareMode sdk.ShareMode, backendMode sdk.BackendMode, trx *sqlx.Tx) (bool, error) { +func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ sdk.ShareMode, backendMode sdk.BackendMode, trx *sqlx.Tx) (bool, error) { if a.cfg.Enforcing { if err := a.str.LimitCheckLock(acctId, trx); err != nil { return false, err } - alcs, err := a.str.FindAppliedLimitClassesForAccount(acctId, trx) + ul, err := a.getUserLimits(acctId, trx) if err != nil { return false, err } - maxShares := a.cfg.Shares - maxReservedShares := a.cfg.ReservedShares - maxUniqueNames := a.cfg.UniqueNames - var lcId *int - var points = -1 - for _, alc := range alcs { - if a.bandwidthClassPoints(alc) > points { - if alc.Shares >= maxShares || alc.ReservedShares >= maxReservedShares || alc.UniqueNames >= maxUniqueNames { - maxShares = alc.Shares - maxReservedShares = alc.ReservedShares - maxUniqueNames = alc.UniqueNames - lcId = &alc.Id - points = a.bandwidthClassPoints(alc) - } - } - } - if lcId == nil { + if ul.resource.IsGlobal() { if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(acctId, trx); err == nil && !empty { lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(acctId, trx) if err != nil { @@ -114,8 +98,8 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, sha } } } else { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, *lcId, trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, *lcId, trx) + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, ul.resource.GetLimitClassId(), trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, ul.resource.GetLimitClassId(), trx) if err != nil { return false, err } @@ -125,7 +109,7 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, sha } } - if maxShares > store.Unlimited || (reserved && maxReservedShares > store.Unlimited) || (reserved && uniqueName && maxUniqueNames > store.Unlimited) { + if ul.resource.GetShares() > store.Unlimited || (reserved && ul.resource.GetReservedShares() > store.Unlimited) || (reserved && uniqueName && ul.resource.GetUniqueNames() > store.Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err @@ -147,15 +131,15 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, sha uniqueNames++ } } - if total+1 > a.cfg.Shares { + if total+1 > ul.resource.GetShares() { logrus.Debugf("account '%d', environment '%d' over shares limit '%d'", acctId, envId, a.cfg.Shares) return false, nil } - if reserved && reserveds+1 > a.cfg.ReservedShares { + if reserved && reserveds+1 > ul.resource.GetReservedShares() { logrus.Debugf("account '%v', environment '%d' over reserved shares limit '%d'", acctId, envId, a.cfg.ReservedShares) return false, nil } - if reserved && uniqueName && uniqueNames+1 > a.cfg.UniqueNames { + if reserved && uniqueName && uniqueNames+1 > ul.resource.GetUniqueNames() { logrus.Debugf("account '%v', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) return false, nil } @@ -177,28 +161,12 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { return false, err } if env.AccountId != nil { - alcs, err := a.str.FindAppliedLimitClassesForAccount(*env.AccountId, trx) + ul, err := a.getUserLimits(*env.AccountId, trx) if err != nil { return false, err } - maxShares := a.cfg.Shares - maxReservedShares := a.cfg.ReservedShares - maxUniqueNames := a.cfg.UniqueNames - var lcId *int - var points = -1 - for _, alc := range alcs { - if a.bandwidthClassPoints(alc) > points { - if alc.Shares >= maxShares || alc.ReservedShares >= maxReservedShares || alc.UniqueNames >= maxUniqueNames { - maxShares = alc.Shares - maxReservedShares = alc.ReservedShares - maxUniqueNames = alc.UniqueNames - lcId = &alc.Id - points = a.bandwidthClassPoints(alc) - } - } - } - if lcId == nil { + if ul.resource.IsGlobal() { if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(*env.AccountId, trx); err == nil && !empty { lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(*env.AccountId, trx) if err != nil { @@ -209,8 +177,8 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { } } } else { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(*env.AccountId, *lcId, trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(*env.AccountId, *lcId, trx) + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(*env.AccountId, ul.resource.GetLimitClassId(), trx); err == nil && !empty { + lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(*env.AccountId, ul.resource.GetLimitClassId(), trx) if err != nil { return false, err } From aee973379cd2d8fb0fa829ef24dd03577e075423 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 11:38:49 -0400 Subject: [PATCH 047/117] fix for bandwidth limit check in CanCreateShare; improved code cleanliness (#606) --- controller/limits/agent.go | 132 +++++++++------------- controller/store/bandwidthLimitJournal.go | 7 -- 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 8f9ec89a..838911c9 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -87,25 +87,14 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s return false, err } - if ul.resource.IsGlobal() { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(acctId, trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(acctId, trx) - if err != nil { - return false, err - } - if lj.Action == store.LimitLimitAction { - return false, nil - } + bwcs := ul.toBandwidthArray(backendMode) + for _, bwc := range bwcs { + latestJe, err := a.isBandwidthClassLimitedForAccount(acctId, bwc, trx) + if err != nil { + return false, err } - } else { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, ul.resource.GetLimitClassId(), trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, ul.resource.GetLimitClassId(), trx) - if err != nil { - return false, err - } - if lj.Action == store.LimitLimitAction { - return false, nil - } + if latestJe != nil { + return false, nil } } @@ -261,50 +250,25 @@ func (a *Agent) enforce(u *metrics.Usage) error { return err } - exceededLc, rxBytes, txBytes, err := a.hasExceededBandwidthLimit(u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) + exceededBwc, rxBytes, txBytes, err := a.anyBandwidthLimitExceeded(u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) if err != nil { return errors.Wrap(err, "error checking limit classes") } - if exceededLc != nil { - enforced := false - var enforcedAt time.Time - - if exceededLc.IsGlobal() { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(int(u.AccountId), trx); err == nil && !empty { - if latest, err := a.str.FindLatestBandwidthLimitJournalForGlobal(int(u.AccountId), trx); err == nil { - enforced = latest.Action == exceededLc.GetLimitAction() - enforcedAt = latest.UpdatedAt - logrus.Debugf("limit '%v' already applied (enforced: %t)", exceededLc, enforced) - } else { - logrus.Errorf("error getting latest global bandwidth journal entry: %v", err) - } - } else { - logrus.Debugf("no bandwidth limit journal entry for '%v'", exceededLc) - } - } else { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(int(u.AccountId), exceededLc.GetLimitClassId(), trx); err == nil && !empty { - if latest, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(int(u.AccountId), exceededLc.GetLimitClassId(), trx); err == nil { - enforced = latest.Action == exceededLc.GetLimitAction() - enforcedAt = latest.UpdatedAt - logrus.Debugf("limit '%v' already applied (enforced: %t)", exceededLc, enforced) - } else { - logrus.Errorf("error getting latest bandwidth limit journal entry for limit class '%d': %v", exceededLc.GetLimitClassId(), err) - } - } else { - logrus.Debugf("no bandwidth limit journal entry for '%v'", exceededLc) - } + if exceededBwc != nil { + latestJe, err := a.isBandwidthClassLimitedForAccount(int(u.AccountId), exceededBwc, trx) + if err != nil { + return err } - - if !enforced { + if latestJe == nil { je := &store.BandwidthLimitJournalEntry{ AccountId: int(u.AccountId), RxBytes: rxBytes, TxBytes: txBytes, - Action: exceededLc.GetLimitAction(), + Action: exceededBwc.GetLimitAction(), } - if !exceededLc.IsGlobal() { - lcId := exceededLc.GetLimitClassId() + if !exceededBwc.IsGlobal() { + lcId := exceededBwc.GetLimitClassId() je.LimitClassId = &lcId } if _, err := a.str.CreateBandwidthLimitJournalEntry(je, trx); err != nil { @@ -314,17 +278,17 @@ func (a *Agent) enforce(u *metrics.Usage) error { if err != nil { return err } - switch exceededLc.GetLimitAction() { + switch exceededBwc.GetLimitAction() { case store.LimitLimitAction: for _, limitAction := range a.limitActions { - if err := limitAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, ul, trx); err != nil { + if err := limitAction.HandleAccount(acct, rxBytes, txBytes, exceededBwc, ul, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(limitAction).String()) } } case store.WarningLimitAction: for _, warningAction := range a.warningActions { - if err := warningAction.HandleAccount(acct, rxBytes, txBytes, exceededLc, ul, trx); err != nil { + if err := warningAction.HandleAccount(acct, rxBytes, txBytes, exceededBwc, ul, trx); err != nil { return errors.Wrapf(err, "%v", reflect.TypeOf(warningAction).String()) } } @@ -333,7 +297,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { return err } } else { - logrus.Debugf("already enforced limit for account '%d' at %v", u.AccountId, enforcedAt) + logrus.Debugf("limit '%v' already applied for '%v' (at: %v)", exceededBwc, acct.Email, latestJe.CreatedAt) } } @@ -406,7 +370,7 @@ func (a *Agent) relax() error { } used := periodBw[bwc.GetPeriodMinutes()] - if !a.limitExceeded(used.rx, used.tx, bwc) { + if !a.transferBytesExceeded(used.rx, used.tx, bwc) { if bwc.GetLimitAction() == store.LimitLimitAction { logrus.Infof("relaxing limit '%v' for '%v'", bwc.String(), accounts[bwje.AccountId].Email) for _, action := range a.relaxActions { @@ -447,7 +411,38 @@ func (a *Agent) relax() error { return nil } -func (a *Agent) hasExceededBandwidthLimit(u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { +func (a *Agent) isBandwidthClassLimitedForAccount(acctId int, bwc store.BandwidthClass, trx *sqlx.Tx) (*store.BandwidthLimitJournalEntry, error) { + if bwc.IsGlobal() { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(acctId, trx); err == nil && !empty { + je, err := a.str.FindLatestBandwidthLimitJournalForGlobal(acctId, trx) + if err != nil { + return nil, err + } + if je.Action == store.LimitLimitAction { + logrus.Infof("account '#%d' over bandwidth for global bandwidth class '%v'", acctId, bwc) + return je, nil + } + } else if err != nil { + return nil, err + } + } else { + if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(acctId, bwc.GetLimitClassId(), trx); err == nil && !empty { + je, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(acctId, bwc.GetLimitClassId(), trx) + if err != nil { + return nil, err + } + if je.Action == store.LimitLimitAction { + logrus.Infof("account '#%d' over bandwidth for limit class '%v'", acctId, bwc) + return je, nil + } + } else if err != nil { + return nil, err + } + } + return nil, nil +} + +func (a *Agent) anyBandwidthLimitExceeded(u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { periodBw := make(map[int]struct { rx int64 tx int64 @@ -473,7 +468,7 @@ func (a *Agent) hasExceededBandwidthLimit(u *metrics.Usage, bwcs []store.Bandwid } period := periodBw[bwc.GetPeriodMinutes()] - if a.limitExceeded(period.rx, period.tx, bwc) { + if a.transferBytesExceeded(period.rx, period.tx, bwc) { selectedLc = bwc rxBytes = period.rx txBytes = period.tx @@ -489,24 +484,7 @@ func (a *Agent) hasExceededBandwidthLimit(u *metrics.Usage, bwcs []store.Bandwid return selectedLc, rxBytes, txBytes, nil } -func (a *Agent) bandwidthClassPoints(bwc store.BandwidthClass) int { - points := 0 - if !bwc.IsGlobal() { - points++ - } - if bwc.GetLimitAction() == store.WarningLimitAction { - points++ - } - if bwc.GetLimitAction() == store.LimitLimitAction { - points += 2 - } - if bwc.GetBackendMode() != "" { - points += 10 - } - return points -} - -func (a *Agent) limitExceeded(rx, tx int64, bwc store.BandwidthClass) bool { +func (a *Agent) transferBytesExceeded(rx, tx int64, bwc store.BandwidthClass) bool { if bwc.GetTxBytes() != store.Unlimited && tx >= bwc.GetTxBytes() { return true } diff --git a/controller/store/bandwidthLimitJournal.go b/controller/store/bandwidthLimitJournal.go index 2fead8d8..5b867a36 100644 --- a/controller/store/bandwidthLimitJournal.go +++ b/controller/store/bandwidthLimitJournal.go @@ -106,13 +106,6 @@ func (str *Store) FindAllLatestBandwidthLimitJournal(trx *sqlx.Tx) ([]*Bandwidth return jes, nil } -func (str *Store) DeleteBandwidthLimitJournal(acctId int, trx *sqlx.Tx) error { - if _, err := trx.Exec("delete from bandwidth_limit_journal where account_id = $1", acctId); err != nil { - return errors.Wrapf(err, "error deleting from bandwidth_limit_journal for account_id = %d", acctId) - } - return nil -} - func (str *Store) DeleteBandwidthLimitJournalEntryForGlobal(acctId int, trx *sqlx.Tx) error { if _, err := trx.Exec("delete from bandwidth_limit_journal where account_id = $1 and limit_class_id is null", acctId); err != nil { return errors.Wrapf(err, "error deleting from bandwidth_limit_journal for account_id = %d and limit_class_id is null", acctId) From efd16c276013fc14915fd6f05f328d69af00efd1 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 12:01:11 -0400 Subject: [PATCH 048/117] ignore scoped backends when unscoped limit class in limit action (#606) --- controller/limits/limitAction.go | 1 + controller/limits/userLimits.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/controller/limits/limitAction.go b/controller/limits/limitAction.go index 0338ad97..32bcef72 100644 --- a/controller/limits/limitAction.go +++ b/controller/limits/limitAction.go @@ -32,6 +32,7 @@ func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.B } ignoreBackends := ul.ignoreBackends(bwc) + logrus.Warnf("ignore backends excluding '%v': %v", bwc, ignoreBackends) for _, env := range envs { shrs, err := a.str.FindSharesForEnvironment(env.Id, trx) if err != nil { diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index bf40b2ca..92c1bab3 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -35,6 +35,12 @@ func (ul *userLimits) ignoreBackends(bwc store.BandwidthClass) map[sdk.BackendMo } } return ignoreBackends + } else { + ignoreBackends := make(map[sdk.BackendMode]bool) + for backendMode := range ul.scopes { + ignoreBackends[backendMode] = true + } + return ignoreBackends } return nil } From afc8d22a9ffa5e50096c19eeec74ab913f16c01b Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 13:29:51 -0400 Subject: [PATCH 049/117] fix for scoped bandwidth limits in agent.CanCreateShare; make relax actions error without failing (#606, #451) --- controller/limits/agent.go | 17 +++++++++++++---- controller/limits/relaxAction.go | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 838911c9..d1885e46 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -87,15 +87,24 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s return false, err } - bwcs := ul.toBandwidthArray(backendMode) - for _, bwc := range bwcs { - latestJe, err := a.isBandwidthClassLimitedForAccount(acctId, bwc, trx) + if scopedBwc, found := ul.scopes[backendMode]; found { + latestScopedJe, err := a.isBandwidthClassLimitedForAccount(acctId, scopedBwc, trx) if err != nil { return false, err } - if latestJe != nil { + if latestScopedJe != nil { return false, nil } + } else { + for _, bwc := range ul.bandwidth { + latestJe, err := a.isBandwidthClassLimitedForAccount(acctId, bwc, trx) + if err != nil { + return false, err + } + if latestJe != nil { + return false, nil + } + } } if ul.resource.GetShares() > store.Unlimited || (reserved && ul.resource.GetReservedShares() > store.Unlimited) || (reserved && uniqueName && ul.resource.GetUniqueNames() > store.Unlimited) { diff --git a/controller/limits/relaxAction.go b/controller/limits/relaxAction.go index f0df0441..454b03e0 100644 --- a/controller/limits/relaxAction.go +++ b/controller/limits/relaxAction.go @@ -44,11 +44,11 @@ func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.B switch shr.ShareMode { case string(sdk.PublicShareMode): if err := relaxPublicShare(a.str, edge, shr, trx); err != nil { - return errors.Wrap(err, "error relaxing public share") + logrus.Errorf("error relaxing public share '%v' for account '%v' (ignoring): %v", shr.Token, acct.Email, err) } case string(sdk.PrivateShareMode): if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil { - return errors.Wrap(err, "error relaxing private share") + logrus.Errorf("error relaxing private share '%v' for account '%v' (ignoring): %v", shr.Token, acct.Email, err) } } } From 60893100a573dbb0b311492342d796251e634da6 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 14:02:28 -0400 Subject: [PATCH 050/117] fix for not relaxing scoped bandwidth clases that are in a limited state (#606) --- controller/limits/relaxAction.go | 21 +++++++++++++++++++-- controller/store/bandwidthLimitJournal.go | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/controller/limits/relaxAction.go b/controller/limits/relaxAction.go index 454b03e0..918090f9 100644 --- a/controller/limits/relaxAction.go +++ b/controller/limits/relaxAction.go @@ -27,6 +27,23 @@ func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.B return errors.Wrapf(err, "error finding environments for account '%v'", acct.Email) } + jes, err := a.str.FindAllLatestBandwidthLimitJournalForAccount(acct.Id, trx) + if err != nil { + return errors.Wrapf(err, "error finding latest bandwidth limit journal entries for account '%v'", acct.Email) + } + limitedBackends := make(map[sdk.BackendMode]bool) + for _, je := range jes { + if je.LimitClassId != nil { + lc, err := a.str.GetLimitClass(*je.LimitClassId, trx) + if err != nil { + return err + } + if lc.BackendMode != nil && lc.LimitAction == store.LimitLimitAction { + limitedBackends[*lc.BackendMode] = true + } + } + } + edge, err := zrokEdgeSdk.Client(a.zCfg) if err != nil { return err @@ -39,8 +56,8 @@ func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.B } for _, shr := range shrs { - // TODO: when relaxing unscoped classes; need to not relax other scoped limits - if !bwc.IsScoped() || bwc.GetBackendMode() == sdk.BackendMode(shr.BackendMode) { + _, stayLimited := limitedBackends[sdk.BackendMode(shr.BackendMode)] + if (!bwc.IsScoped() && !stayLimited) || bwc.GetBackendMode() == sdk.BackendMode(shr.BackendMode) { switch shr.ShareMode { case string(sdk.PublicShareMode): if err := relaxPublicShare(a.str, edge, shr, trx); err != nil { diff --git a/controller/store/bandwidthLimitJournal.go b/controller/store/bandwidthLimitJournal.go index 5b867a36..b38f8429 100644 --- a/controller/store/bandwidthLimitJournal.go +++ b/controller/store/bandwidthLimitJournal.go @@ -90,6 +90,22 @@ func (str *Store) FindAllBandwidthLimitJournal(trx *sqlx.Tx) ([]*BandwidthLimitJ return jes, nil } +func (str *Store) FindAllLatestBandwidthLimitJournalForAccount(acctId int, trx *sqlx.Tx) ([]*BandwidthLimitJournalEntry, error) { + rows, err := trx.Queryx("select * from bandwidth_limit_journal where account_id = $1", acctId) + if err != nil { + return nil, errors.Wrap(err, "error finding all for account from bandwidth_limit_journal") + } + var jes []*BandwidthLimitJournalEntry + for rows.Next() { + je := &BandwidthLimitJournalEntry{} + if err := rows.StructScan(je); err != nil { + return nil, errors.Wrap(err, "error scanning bandwidth_limit_journal") + } + jes = append(jes, je) + } + return jes, nil +} + func (str *Store) FindAllLatestBandwidthLimitJournal(trx *sqlx.Tx) ([]*BandwidthLimitJournalEntry, error) { rows, err := trx.Queryx("select id, account_id, limit_class_id, action, rx_bytes, tx_bytes, created_at, updated_at from bandwidth_limit_journal where id in (select max(id) as id from bandwidth_limit_journal group by account_id)") if err != nil { From a389674d512fef9e2f4c0d19ace41bf36e191b40 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 15:05:18 -0400 Subject: [PATCH 051/117] scoped classes now include resource limits (#606) --- controller/limits/agent.go | 12 ++++++++---- controller/limits/userLimits.go | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index d1885e46..5d2b2303 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -107,7 +107,11 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s } } - if ul.resource.GetShares() > store.Unlimited || (reserved && ul.resource.GetReservedShares() > store.Unlimited) || (reserved && uniqueName && ul.resource.GetUniqueNames() > store.Unlimited) { + rc := ul.resource + if scopeRc, found := ul.scopes[backendMode]; found { + rc = scopeRc + } + if rc.GetShares() > store.Unlimited || (reserved && rc.GetReservedShares() > store.Unlimited) || (reserved && uniqueName && rc.GetUniqueNames() > store.Unlimited) { envs, err := a.str.FindEnvironmentsForAccount(acctId, trx) if err != nil { return false, err @@ -129,15 +133,15 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s uniqueNames++ } } - if total+1 > ul.resource.GetShares() { + if total+1 > rc.GetShares() { logrus.Debugf("account '%d', environment '%d' over shares limit '%d'", acctId, envId, a.cfg.Shares) return false, nil } - if reserved && reserveds+1 > ul.resource.GetReservedShares() { + if reserved && reserveds+1 > rc.GetReservedShares() { logrus.Debugf("account '%v', environment '%d' over reserved shares limit '%d'", acctId, envId, a.cfg.ReservedShares) return false, nil } - if reserved && uniqueName && uniqueNames+1 > ul.resource.GetUniqueNames() { + if reserved && uniqueName && uniqueNames+1 > rc.GetUniqueNames() { logrus.Debugf("account '%v', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) return false, nil } diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index 92c1bab3..3b40e256 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -11,7 +11,7 @@ import ( type userLimits struct { resource store.ResourceCountClass bandwidth []store.BandwidthClass - scopes map[sdk.BackendMode]store.BandwidthClass + scopes map[sdk.BackendMode]*store.LimitClass } func (ul *userLimits) toBandwidthArray(backendMode sdk.BackendMode) []store.BandwidthClass { @@ -50,7 +50,7 @@ func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { cfgBwcs := newConfigBandwidthClasses(a.cfg.Bandwidth) bwWarning := cfgBwcs[0] bwLimit := cfgBwcs[1] - scopes := map[sdk.BackendMode]store.BandwidthClass{} + scopes := make(map[sdk.BackendMode]*store.LimitClass) alcs, err := a.str.FindAppliedLimitClassesForAccount(acctId, trx) if err != nil { @@ -65,10 +65,10 @@ func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { } else { bwLimit = alc } - } else if a.isScopedBandwidthClass(alc) { + } else if a.isScopedLimitClass(alc) { scopes[*alc.BackendMode] = alc } else { - logrus.Warnf("unknown type of limit class '%v'", alc) + logrus.Warnf("unknown type of limit class '%v' for account '#%d'", alc, acctId) } } @@ -107,11 +107,11 @@ func (a *Agent) isUnscopedBandwidthClass(alc *store.LimitClass) bool { return true } -func (a *Agent) isScopedBandwidthClass(alc *store.LimitClass) bool { +func (a *Agent) isScopedLimitClass(alc *store.LimitClass) bool { if alc.BackendMode == nil { return false } - if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited { + if alc.Environments > store.Unlimited { return false } if alc.PeriodMinutes < 1 { From 79053e1b28cab0df4662d3d7750d583e39eaea4e Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 7 Jun 2024 16:02:40 -0400 Subject: [PATCH 052/117] fully remove share mode from scope concept; add performance indexes (#606) --- controller/store/limitClass.go | 15 ++------------- .../sql/postgresql/022_v0_4_31_limit_classes.sql | 6 ++++-- .../sql/sqlite3/022_v0_4_31_limit_classes.sql | 6 ++++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index ef0991f8..0c7df466 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -36,7 +36,6 @@ type BandwidthClass interface { type LimitClass struct { Model - ShareMode *sdk.ShareMode BackendMode *sdk.BackendMode Environments int Shares int @@ -77,13 +76,6 @@ func (lc LimitClass) GetUniqueNames() int { return lc.UniqueNames } -func (lc LimitClass) GetShareMode() sdk.ShareMode { - if lc.ShareMode == nil { - return "" - } - return *lc.ShareMode -} - func (lc LimitClass) GetBackendMode() sdk.BackendMode { if lc.BackendMode == nil { return "" @@ -113,9 +105,6 @@ func (lc LimitClass) GetLimitAction() LimitAction { func (lc LimitClass) String() string { out := fmt.Sprintf("LimitClass Date: Fri, 7 Jun 2024 16:14:47 -0400 Subject: [PATCH 053/117] fix bandwidth limit journal test (#606) --- controller/store/bandwidthLimitJournal_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/controller/store/bandwidthLimitJournal_test.go b/controller/store/bandwidthLimitJournal_test.go index da8fe784..991b6ba4 100644 --- a/controller/store/bandwidthLimitJournal_test.go +++ b/controller/store/bandwidthLimitJournal_test.go @@ -38,7 +38,6 @@ func TestBandwidthLimitJournal(t *testing.T) { assert.Equal(t, int64(2048), latestJe.TxBytes) lc := &LimitClass{ - ShareMode: new(sdk.ShareMode), BackendMode: new(sdk.BackendMode), PeriodMinutes: 60, RxBytes: 4096, @@ -46,7 +45,6 @@ func TestBandwidthLimitJournal(t *testing.T) { TotalBytes: 10240, LimitAction: LimitLimitAction, } - *lc.ShareMode = sdk.PrivateShareMode *lc.BackendMode = sdk.ProxyBackendMode lcId, err := str.CreateLimitClass(lc, trx) assert.NoError(t, err) From 883fe92848ca6113ced9fd3eb3e007450eb408ca Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 12:00:12 -0400 Subject: [PATCH 054/117] logging tweaks and improvements releated to limits agent (#606) --- CHANGELOG.md | 2 +- controller/limits/agent.go | 23 ++++++++++++----------- controller/limits/bandwidthClass.go | 7 ++++--- controller/limits/limitAction.go | 3 --- controller/limits/relaxAction.go | 2 +- controller/store/limitClass.go | 9 +++++---- ui/middleware.go | 2 -- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1691092..e4e6b541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.4.31 -FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606) +FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606). This new feature allows for extensive limits customization on a per-user basis, with fallback to the global defaults in the controller configuration. FIX: Correct the syntax for the Docker and Linux zrok-share "frontdoor" service that broke OAuth email address pattern matching diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 5d2b2303..7bb8023f 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -7,6 +7,7 @@ import ( "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/controller/zrokEdgeSdk" "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/openziti/zrok/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "reflect" @@ -134,15 +135,15 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s } } if total+1 > rc.GetShares() { - logrus.Debugf("account '%d', environment '%d' over shares limit '%d'", acctId, envId, a.cfg.Shares) + logrus.Debugf("account '#%d', environment '%d' over shares limit '%d'", acctId, envId, a.cfg.Shares) return false, nil } if reserved && reserveds+1 > rc.GetReservedShares() { - logrus.Debugf("account '%v', environment '%d' over reserved shares limit '%d'", acctId, envId, a.cfg.ReservedShares) + logrus.Debugf("account '#%d', environment '%d' over reserved shares limit '%d'", acctId, envId, a.cfg.ReservedShares) return false, nil } if reserved && uniqueName && uniqueNames+1 > rc.GetUniqueNames() { - logrus.Debugf("account '%v', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) + logrus.Debugf("account '#%d', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) return false, nil } logrus.Infof("total = %d", total) @@ -263,7 +264,7 @@ func (a *Agent) enforce(u *metrics.Usage) error { return err } - exceededBwc, rxBytes, txBytes, err := a.anyBandwidthLimitExceeded(u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) + exceededBwc, rxBytes, txBytes, err := a.anyBandwidthLimitExceeded(acct, u, ul.toBandwidthArray(sdk.BackendMode(shr.BackendMode))) if err != nil { return errors.Wrap(err, "error checking limit classes") } @@ -408,7 +409,7 @@ func (a *Agent) relax() error { } } } else { - logrus.Infof("account '%v' still over limit: '%v' with rx: %d, tx: %d, total: %d", accounts[bwje.AccountId].Email, bwc, used.rx, used.tx, used.rx+used.tx) + logrus.Infof("'%v' still over limit: '%v' with rx: %v, tx: %v, total: %v", accounts[bwje.AccountId].Email, bwc, util.BytesToSize(used.rx), util.BytesToSize(used.tx), util.BytesToSize(used.rx+used.tx)) } } } else { @@ -432,7 +433,7 @@ func (a *Agent) isBandwidthClassLimitedForAccount(acctId int, bwc store.Bandwidt return nil, err } if je.Action == store.LimitLimitAction { - logrus.Infof("account '#%d' over bandwidth for global bandwidth class '%v'", acctId, bwc) + logrus.Debugf("account '#%d' over bandwidth for global bandwidth class '%v'", acctId, bwc) return je, nil } } else if err != nil { @@ -445,7 +446,7 @@ func (a *Agent) isBandwidthClassLimitedForAccount(acctId int, bwc store.Bandwidt return nil, err } if je.Action == store.LimitLimitAction { - logrus.Infof("account '#%d' over bandwidth for limit class '%v'", acctId, bwc) + logrus.Debugf("account '#%d' over bandwidth for limit class '%v'", acctId, bwc) return je, nil } } else if err != nil { @@ -455,7 +456,7 @@ func (a *Agent) isBandwidthClassLimitedForAccount(acctId int, bwc store.Bandwidt return nil, nil } -func (a *Agent) anyBandwidthLimitExceeded(u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { +func (a *Agent) anyBandwidthLimitExceeded(acct *store.Account, u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { periodBw := make(map[int]struct { rx int64 tx int64 @@ -469,7 +470,7 @@ func (a *Agent) anyBandwidthLimitExceeded(u *metrics.Usage, bwcs []store.Bandwid if _, found := periodBw[bwc.GetPeriodMinutes()]; !found { rx, tx, err := a.ifx.totalRxTxForAccount(u.AccountId, time.Minute*time.Duration(bwc.GetPeriodMinutes())) if err != nil { - return nil, 0, 0, errors.Wrapf(err, "error getting rx/tx for account '%d'", u.AccountId) + return nil, 0, 0, errors.Wrapf(err, "error getting rx/tx for account '%v'", acct.Email) } periodBw[bwc.GetPeriodMinutes()] = struct { rx int64 @@ -486,12 +487,12 @@ func (a *Agent) anyBandwidthLimitExceeded(u *metrics.Usage, bwcs []store.Bandwid rxBytes = period.rx txBytes = period.tx } else { - logrus.Debugf("limit ok '%v' with rx: %d, tx: %d, total: %d", bwc, period.rx, period.tx, period.rx+period.tx) + logrus.Debugf("'%v' limit ok '%v' with rx: %v, tx: %v, total: %v", acct.Email, bwc, util.BytesToSize(period.rx), util.BytesToSize(period.tx), util.BytesToSize(period.rx+period.tx)) } } if selectedLc != nil { - logrus.Infof("exceeded limit '%v' with rx: %d, tx: %d, total: %d", selectedLc, rxBytes, txBytes, rxBytes+txBytes) + logrus.Infof("'%v' exceeded limit '%v' with rx: %v, tx: %v, total: %v", acct.Email, selectedLc, util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes)) } return selectedLc, rxBytes, txBytes, nil diff --git a/controller/limits/bandwidthClass.go b/controller/limits/bandwidthClass.go index fca22469..49f84535 100644 --- a/controller/limits/bandwidthClass.go +++ b/controller/limits/bandwidthClass.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/openziti/zrok/controller/store" "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/openziti/zrok/util" ) type configBandwidthClass struct { @@ -70,13 +71,13 @@ func (bc *configBandwidthClass) GetLimitAction() store.LimitAction { func (bc *configBandwidthClass) String() string { out := fmt.Sprintf("ConfigClass store.Unlimited { - out += fmt.Sprintf(", rxBytes: %d", bc.bw.Rx) + out += fmt.Sprintf(", rxBytes: %v", util.BytesToSize(bc.bw.Rx)) } if bc.bw.Tx > store.Unlimited { - out += fmt.Sprintf(", txBytes: %d", bc.bw.Tx) + out += fmt.Sprintf(", txBytes: %v", util.BytesToSize(bc.bw.Tx)) } if bc.bw.Total > store.Unlimited { - out += fmt.Sprintf(", totalBytes: %d", bc.bw.Total) + out += fmt.Sprintf(", totalBytes: %v", util.BytesToSize(bc.bw.Total)) } out += fmt.Sprintf(", limitAction: %s>", bc.limitAction) return out diff --git a/controller/limits/limitAction.go b/controller/limits/limitAction.go index 32bcef72..f0392239 100644 --- a/controller/limits/limitAction.go +++ b/controller/limits/limitAction.go @@ -19,8 +19,6 @@ func newLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *limitAction { } func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.BandwidthClass, ul *userLimits, trx *sqlx.Tx) error { - logrus.Infof("limiting '%v'", acct.Email) - envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) if err != nil { return errors.Wrapf(err, "error finding environments for account '%v'", acct.Email) @@ -32,7 +30,6 @@ func (a *limitAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.B } ignoreBackends := ul.ignoreBackends(bwc) - logrus.Warnf("ignore backends excluding '%v': %v", bwc, ignoreBackends) for _, env := range envs { shrs, err := a.str.FindSharesForEnvironment(env.Id, trx) if err != nil { diff --git a/controller/limits/relaxAction.go b/controller/limits/relaxAction.go index 918090f9..8a2a8572 100644 --- a/controller/limits/relaxAction.go +++ b/controller/limits/relaxAction.go @@ -20,7 +20,7 @@ func newRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *relaxAction { } func (a *relaxAction) HandleAccount(acct *store.Account, _, _ int64, bwc store.BandwidthClass, _ *userLimits, trx *sqlx.Tx) error { - logrus.Infof("relaxing '%v'", acct.Email) + logrus.Debugf("relaxing '%v'", acct.Email) envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx) if err != nil { diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 0c7df466..a41eb2de 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/jmoiron/sqlx" "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/openziti/zrok/util" "github.com/pkg/errors" ) @@ -104,7 +105,7 @@ func (lc LimitClass) GetLimitAction() LimitAction { } func (lc LimitClass) String() string { - out := fmt.Sprintf("LimitClass Unlimited { - out += fmt.Sprintf(", rxBytes: %d", lc.RxBytes) + out += fmt.Sprintf(", rxBytes: %v", util.BytesToSize(lc.RxBytes)) } if lc.TxBytes > Unlimited { - out += fmt.Sprintf(", txBytes: %d", lc.TxBytes) + out += fmt.Sprintf(", txBytes: %v", util.BytesToSize(lc.TxBytes)) } if lc.TotalBytes > Unlimited { - out += fmt.Sprintf(", totalBytes: %d", lc.TotalBytes) + out += fmt.Sprintf(", totalBytes: %v", util.BytesToSize(lc.TotalBytes)) } out += fmt.Sprintf(", limitAction: '%v'>", lc.LimitAction) return out diff --git a/ui/middleware.go b/ui/middleware.go index 4ca0a9f2..e31cf967 100644 --- a/ui/middleware.go +++ b/ui/middleware.go @@ -1,7 +1,6 @@ package ui import ( - "github.com/sirupsen/logrus" "io/fs" "net/http" "os" @@ -10,7 +9,6 @@ import ( ) func Middleware(handler http.Handler, healthCheck func(w http.ResponseWriter, r *http.Request)) http.Handler { - logrus.Infof("building") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/v1") { handler.ServeHTTP(w, r) From 88fffdc39bafa00c2bec6e82f1f4fd5539527e7a Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 12:02:05 -0400 Subject: [PATCH 055/117] changelog tweaks --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84aa981b..4e772864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,9 @@ FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606). This new feature allows for extensive limits customization on a per-user basis, with fallback to the global defaults in the controller configuration. -FIX: Correct the syntax for the Docker and Linux zrok-share "frontdoor" service that broke OAuth email address pattern matching +CHANGE: Log messages that said `backend proxy endpoint` were clarified to say `backend target`. -CHANGE: log messages that said "backend proxy endpoint" were clarified to say "backend target" +FIX: Correct the syntax for the Docker and Linux zrok-share "frontdoor" service that broke OAuth email address pattern matching. ## v0.4.30 From 2acb8ea056b24fe5e099a0a330c17b279c554596 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 13:46:07 -0400 Subject: [PATCH 056/117] docs (#606) --- .../metrics-and-limits/configuring-limits.md | 73 ++++++++++--------- etc/ctrl.yml | 2 +- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index a48ad3bf..72a31d32 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -6,49 +6,54 @@ sidebar_position: 40 > If you have not yet configured [metrics](configuring-metrics.md), please visit the [metrics guide](configuring-metrics.md) first before working through the limits configuration. -The limits facility in `zrok` is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured thresholds. +:::note +This guide is current as of zrok version `v0.4.31`. +::: -Take this `zrok` controller configuration stanza as an example: +The limits facility in zrok is used to control the amount of resources that can be consumed by any account in a service instance. + +Limits can be specified that control the number of environments, shares, reserved shares, and unique names. Limits that control the number of resources are called _resource count limits_. + +Limits can be specified that control the amount of data that can be transferred for different types of share backend modes. Limits that control the amount of data that can be transferred are called _bandwidth limits_. + +The limits facility in zrok is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured thresholds. + +zrok limits can be specified _globally_, applying to all users in a service instance. Individual limits can be specified and applied to individual accounts using a new facility called _limit classes_. Limit classes can be used to specify resource count and bandwidth limit defaults per-account. Separate limits for each type share backend can also be specified and applied to user accounts. + +## The Global Configuration + +The reference configuration for the zrok controller (found at [`etc/ctrl.yaml`](https://github.com/openziti/zrok/blob/main/etc/ctrl.yml) in the [repository](https://github.com/openziti/zrok)) contains the global limits configuration, which looks like this: ```yaml +# Service instance limits global configuration. +# +# See `docs/guides/metrics-and-limits/configuring-limits.md` for details. +# limits: - enforcing: true - cycle: 1m environments: -1 shares: -1 + reserved_shares: -1 + unique_names: -1 bandwidth: - per_account: - period: 5m - warning: - rx: -1 - tx: -1 - total: 7242880 - limit: - rx: -1 - tx: -1 - total: 10485760 - per_environment: - period: 5m - warning: - rx: -1 - tx: -1 - total: -1 - limit: - rx: -1 - tx: -1 - total: -1 - per_share: - period: 5m - warning: - rx: -1 - tx: -1 - total: -1 - limit: - rx: -1 - tx: -1 - total: -1 + period: 5m + warning: + rx: -1 + tx: -1 + total: 7242880 + limit: + rx: -1 + tx: -1 + total: 10485760 + enforcing: false + cycle: 5m ``` +The `environments`, `shares`, `reserved_shares`, and `unique_names` specify the resource count limits, globally for the service instance. + +:::note +A value of `-1` appearing in the limits configuration mean the value is _unlimited_. +::: + ## The Global Controls The `enforcing` boolean will globally enable or disable limits for the controller. diff --git a/etc/ctrl.yml b/etc/ctrl.yml index 4ecd5392..8fb7d801 100644 --- a/etc/ctrl.yml +++ b/etc/ctrl.yml @@ -74,7 +74,7 @@ invites: # token_contact: invite@zrok.io -# Service instance limits configuration. +# Service instance limits global configuration. # # See `docs/guides/metrics-and-limits/configuring-limits.md` for details. # From a65df8c4e40216d0e4fde1d0f676ba6d50f128cf Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 13:53:30 -0400 Subject: [PATCH 057/117] docs iteration (#606) --- .../metrics-and-limits/configuring-limits.md | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 72a31d32..8caae1fa 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -4,12 +4,14 @@ sidebar_position: 40 # Configuring Limits -> If you have not yet configured [metrics](configuring-metrics.md), please visit the [metrics guide](configuring-metrics.md) first before working through the limits configuration. - :::note This guide is current as of zrok version `v0.4.31`. ::: +:::warning +If you have not yet configured [metrics](configuring-metrics.md), please visit the [metrics guide](configuring-metrics.md) first before working through the limits configuration. +::: + The limits facility in zrok is used to control the amount of resources that can be consumed by any account in a service instance. Limits can be specified that control the number of environments, shares, reserved shares, and unique names. Limits that control the number of resources are called _resource count limits_. @@ -48,27 +50,26 @@ limits: cycle: 5m ``` -The `environments`, `shares`, `reserved_shares`, and `unique_names` specify the resource count limits, globally for the service instance. - :::note A value of `-1` appearing in the limits configuration mean the value is _unlimited_. ::: -## The Global Controls +The `enforcing` boolean specifies whether or not limits are enabled in the service instance. By default, limits is disabled. No matter what else is configured in this stanza, if `enforcing` is set to `false`, there will be no limits placed on any account in the service instance. -The `enforcing` boolean will globally enable or disable limits for the controller. +The `cycle` value controls how frequently the limits agent will evaluate enforced limits. When a user exceeds a limit and has their shares disabled, the limits agent will evaluate their bandwidth usage on this interval looking to "relax" the limit once their usage falls below the threshold. -The `cycle` value controls how frequently the limits system will look for limited resources to re-enable. +### Global Resouce Count Limits -## Resource Limits +The `environments`, `shares`, `reserved_shares`, and `unique_names` specify the resource count limits, globally for the service instance. -The `environments` and `shares` values control the number of environments and shares that are allowed per-account. Any limit value can be set to `-1`, which means _unlimited_. +These resource counts will be applied to all users in the service instance by default. -## Bandwidth Limits +## Global Bandwidth Limits + +The `bandwidth` section defines the global bandwidth limits for all users in the service instance. The `bandwidth` section is designed to provide a configurable system for controlling the amount of data transfer that can be performed by users of the `zrok` service instance. The bandwidth limits are configurable for each share, environment, and account. -`per_account`, `per_environment`, and `per_share` are all configured the same way: The `period` specifies the time window for the bandwidth limit. See the documentation for [`time.Duration.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for details about the format used for these durations. If the `period` is set to 5 minutes, then the limits implementation will monitor the send and receive traffic for the resource (share, environment, or account) for the last 5 minutes, and if the amount of data is greater than either the `warning` or the `limit` threshold, action will be taken. From 6f8e43d666eab7e628df6df73eec40218607dcac Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 14:03:00 -0400 Subject: [PATCH 058/117] docs (#606) --- .../metrics-and-limits/configuring-limits.md | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 8caae1fa..db42936a 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -12,13 +12,15 @@ This guide is current as of zrok version `v0.4.31`. If you have not yet configured [metrics](configuring-metrics.md), please visit the [metrics guide](configuring-metrics.md) first before working through the limits configuration. ::: -The limits facility in zrok is used to control the amount of resources that can be consumed by any account in a service instance. +## Understanding the zrok Limits Agent + +The limits agent in zrok is used to control the amount of resources that can be consumed by any account in a service instance. Limits can be specified that control the number of environments, shares, reserved shares, and unique names. Limits that control the number of resources are called _resource count limits_. Limits can be specified that control the amount of data that can be transferred for different types of share backend modes. Limits that control the amount of data that can be transferred are called _bandwidth limits_. -The limits facility in zrok is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured thresholds. +The limits agent in zrok is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured thresholds. zrok limits can be specified _globally_, applying to all users in a service instance. Individual limits can be specified and applied to individual accounts using a new facility called _limit classes_. Limit classes can be used to specify resource count and bandwidth limit defaults per-account. Separate limits for each type share backend can also be specified and applied to user accounts. @@ -64,30 +66,23 @@ The `environments`, `shares`, `reserved_shares`, and `unique_names` specify the These resource counts will be applied to all users in the service instance by default. -## Global Bandwidth Limits +### Global Bandwidth Limits The `bandwidth` section defines the global bandwidth limits for all users in the service instance. -The `bandwidth` section is designed to provide a configurable system for controlling the amount of data transfer that can be performed by users of the `zrok` service instance. The bandwidth limits are configurable for each share, environment, and account. +There are two levels of bandwidth limits that can be specified in the global configuration. The first limit defines a _warning_ threshold where the user will receive an email that they are using increased data transfer amounts and will ultimately be subject to a limit. +The second limit defines the the actual _limit_ threshold, where the limits agent will disabled traffic for the account's shares. -The `period` specifies the time window for the bandwidth limit. See the documentation for [`time.Duration.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for details about the format used for these durations. If the `period` is set to 5 minutes, then the limits implementation will monitor the send and receive traffic for the resource (share, environment, or account) for the last 5 minutes, and if the amount of data is greater than either the `warning` or the `limit` threshold, action will be taken. +Bandwidth limits can be specified in terms of `tx` (or _transmitted_ data), `rx` (or _received_ data), and the `total` bytes that are sent in either direction. If you only want to set the `total` transferred limit, you can set `rx` and `tx` to `-1` (for _unlimited_). You can configure any combination of these these values at either the limit or warning levels. -The `rx` value is the number of bytes _received_ by the resource. The `tx` value is the number of bytes _transmitted_ by the resource. And `total` is the combined `rx`+`tx` value. +The `period` specifies the time window for the bandwidth limit. See the documentation for [`time.Duration.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for details about the format used for these durations. If the `period` is set to 5 minutes, then the limits agent will monitor the transmitted and receivde traffic for the account for the last 5 minutes, and if the amount of data is greater than either the `warning` or the `limit` threshold, action will be taken. -If the traffic quantity is greater than the `warning` threshold, the user will receive an email notification letting them know that their data transfer size is rising and will eventually be limited (the email details the limit threshold). +## Limit Actions -If the traffic quantity is greater than the `limit` threshold, the resources will be limited until the traffic in the window (the last 5 minutes in our example) falls back below the `limit` threshold. +When an account exceeds a bandwidth limit, the limits agent will seek to limit the affected shares (based on the combination of global configuration, unscoped limit classes, and scoped limit classes). It applies the limit by removing the underlying OpenZiti dial policies for any frontends that are trying to access the share. -### Limit Actions - -When a resource is limited, the actions taken differ depending on what kind of resource is being limited. - -When a share is limited, the dial service policies for that share are removed. No other action is taken. This means that public frontends will simply return a `404` as if the share is no longer there. Private frontends will also return `404` errors. When the limit is relaxed, the dial policies are put back in place and the share will continue operating normally. - -When an environment is limited, all of the shares in that environment become limited, and the user is not able to create new shares in that environment. When the limit is relaxed, all of the share limits are relaxed and the user is again able to add shares to the environment. - -When an account is limited, all of the environments in that account become limited (limiting all of the shares), and the user is not able to create new environments or shares. When the limit is relaxed, all of the environments and shares will return to normal operation. +This means that public frontends will simply return a `404` as if the share is no longer there. Private frontends will also return `404` errors. When the limit is relaxed, the dial policies are put back in place and the share will continue operating normally. ## Unlimited Accounts From cb306cc0fb3367427d8912ead8af7c022e39ad6c Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 16:03:07 -0400 Subject: [PATCH 059/117] schema docs? (#606) --- .../metrics-and-limits/configuring-limits.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index db42936a..193e16fc 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -78,6 +78,32 @@ Bandwidth limits can be specified in terms of `tx` (or _transmitted_ data), `rx` The `period` specifies the time window for the bandwidth limit. See the documentation for [`time.Duration.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for details about the format used for these durations. If the `period` is set to 5 minutes, then the limits agent will monitor the transmitted and receivde traffic for the account for the last 5 minutes, and if the amount of data is greater than either the `warning` or the `limit` threshold, action will be taken. +## Limit Classes + +The zrok limits agent includes a concept called _limit classes_. Limit classes can be used to define resource count and bandwidth limits that can be selectively applied to individual accounts in a service instance. + +Limit classes are created by creating a record in the `limit_classes` table in the zrok controller database. The table has this schema: + +``` + Table "public.limit_classes" + Column | Type | Collation | Nullable | Default +-----------------+--------------------------+-----------+----------+------------------------------------------- + id | integer | | not null | nextval('limit_classes_id_seq'::regclass) + backend_mode | backend_mode | | | + environments | integer | | not null | '-1'::integer + shares | integer | | not null | '-1'::integer + reserved_shares | integer | | not null | '-1'::integer + unique_names | integer | | not null | '-1'::integer + period_minutes | integer | | not null | 1440 + rx_bytes | bigint | | not null | '-1'::integer + tx_bytes | bigint | | not null | '-1'::integer + total_bytes | bigint | | not null | '-1'::integer + limit_action | limit_action | | not null | 'limit'::limit_action + created_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP + updated_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP + deleted | boolean | | not null | false +``` + ## Limit Actions When an account exceeds a bandwidth limit, the limits agent will seek to limit the affected shares (based on the combination of global configuration, unscoped limit classes, and scoped limit classes). It applies the limit by removing the underlying OpenZiti dial policies for any frontends that are trying to access the share. From 2d944e965a24813af3b07eb8f34a6d18bd55a891 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 16:30:02 -0400 Subject: [PATCH 060/117] roughed in full limits doc? (#606) --- .../metrics-and-limits/configuring-limits.md | 107 +++++++++++++++--- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 193e16fc..841cb778 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -84,26 +84,83 @@ The zrok limits agent includes a concept called _limit classes_. Limit classes c Limit classes are created by creating a record in the `limit_classes` table in the zrok controller database. The table has this schema: +```sql +CREATE TABLE public.limit_classes ( + id integer NOT NULL, + backend_mode public.backend_mode, + environments integer DEFAULT '-1'::integer NOT NULL, + shares integer DEFAULT '-1'::integer NOT NULL, + reserved_shares integer DEFAULT '-1'::integer NOT NULL, + unique_names integer DEFAULT '-1'::integer NOT NULL, + period_minutes integer DEFAULT 1440 NOT NULL, + rx_bytes bigint DEFAULT '-1'::integer NOT NULL, + tx_bytes bigint DEFAULT '-1'::integer NOT NULL, + total_bytes bigint DEFAULT '-1'::integer NOT NULL, + limit_action public.limit_action DEFAULT 'limit'::public.limit_action NOT NULL, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted boolean DEFAULT false NOT NULL +); + ``` - Table "public.limit_classes" - Column | Type | Collation | Nullable | Default ------------------+--------------------------+-----------+----------+------------------------------------------- - id | integer | | not null | nextval('limit_classes_id_seq'::regclass) - backend_mode | backend_mode | | | - environments | integer | | not null | '-1'::integer - shares | integer | | not null | '-1'::integer - reserved_shares | integer | | not null | '-1'::integer - unique_names | integer | | not null | '-1'::integer - period_minutes | integer | | not null | 1440 - rx_bytes | bigint | | not null | '-1'::integer - tx_bytes | bigint | | not null | '-1'::integer - total_bytes | bigint | | not null | '-1'::integer - limit_action | limit_action | | not null | 'limit'::limit_action - created_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP - updated_at | timestamp with time zone | | not null | CURRENT_TIMESTAMP - deleted | boolean | | not null | false + +This schema supports constructing the 3 different types of limits classes that the system supports. + +After defining a limit class in the database, it can be applied to specific user accounts (overriding the relevant parts of the global configuration) by inserting a row into the `applied_limit_classes` table: + +```sql +CREATE TABLE public.applied_limit_classes ( + id integer NOT NULL, + account_id integer NOT NULL, + limit_class_id integer NOT NULL, + created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted boolean DEFAULT false NOT NULL +); ``` +Create a row in this table linking the `account_id` to the `limit_class_id` to apply the limit class to a specific user account. + +### Unscoped Resource Count Classes + +To support overriding the resource count limits defined in the global limits configuration, a site administrator can create a limit class by inserting a row into the `limit_classes` table structured like this: + +```sql +insert into limit_classes (environments, shares, reserved_shares, unique_names) values (1, 1, 1, 1); +``` + +This creates a limit class that sets the `environments`, `shares`, `reserved_shares`, and `unique_names` all to `1`. + +When this limit class is applied to a user account those values would override the default resource count values configured globally. + +Applying an unscoped resource count class _does not_ affect the bandwidth limits (either globally configured, or via a limit class). + +### Unscoped Bandwidth Classes + +To support overriding the bandwidth limits defined in the global configuration, a site administrator can create a limit class by inserting a row into the `limit_classes` table structured like this: + +```sql +insert into limit_classes (period_minutes, total_bytes, limit_action) values (2, 204800, 'limit'); +``` + +This inserts a limit class that allows for a total bandwidth transfer of `204800` bytes every `2` minutes. + +When this limit class is applied to a user account, those values would override the default bandwidth values configured globally. + +Applying an unscoped bandwidth class _does not_ affect the resource count limits (either globally configured, or via a limit class). + +### Scoped Classes + +A scoped limit class specifies _both_ the resource counts (`shares`, `reserved_shares`, and `unique_names`, but *NOT* `environments`) for a *specific* backend mode. Insert a row like this: + +```sql +insert into limit_classes (backend_mode, shares, reserved_shares, unique_names, period_minutes, total_bytes, limit_action) values ('web', 2, 1, 1, 2, 4096000, 'limit'); +``` + +Scoped limits are designed to _increase_ the limits for a specific backend mode beyond what the global configuration and the unscoped classes provide. The general approach is to use the global configuration and the unscoped classes to provide the general account limits, and then the scoped classes can be used to further increase (or potentially _decrease_) the limits for a specific backend mode. + +If a scoped limit class exists for a specific backend mode, then the limits agent will use that limit in making a decision about limiting the resource count or bandwidth. All other types of shares will fall back to the unscoped classes or the global configuration. + ## Limit Actions When an account exceeds a bandwidth limit, the limits agent will seek to limit the affected shares (based on the combination of global configuration, unscoped limit classes, and scoped limit classes). It applies the limit by removing the underlying OpenZiti dial policies for any frontends that are trying to access the share. @@ -112,4 +169,18 @@ This means that public frontends will simply return a `404` as if the share is n ## Unlimited Accounts -The `accounts` table in the database includes a `limitless` column. When this column is set to `true` the account is not subject to any of the limits in the system. \ No newline at end of file +The `accounts` table in the database includes a `limitless` column. When this column is set to `true` the account is not subject to any of the limits in the system. + +## Caveats + +There are a number of caveats that are important to understand when using the limits agent with more complicated limits scenarios: + +### Aggregate Bandwidth + +The zrok limits agent is a work in progress. The system currently does not track bandwidth individually for each backend mode type, which means all bandwidth values are aggregated between all of the share types that an account might be using. This will likely change in an upcoming release. + +### Administration Through SQL + +There are currently no administrative API endpoints (or corresponding CLI tools) to support creating and applying limit classes in the current release. The limits agent infrastructure was designed to support software integrations that directly manipulate the underlying database structures. + +A future release may provide API and CLI tooling to support the human administration of the limits agent. \ No newline at end of file From 98e88744ffec806333aa95147aa39ae59af0f0ad Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 17:08:24 -0400 Subject: [PATCH 061/117] enable_locking (#287) --- .../metrics-and-limits/configuring-limits.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 841cb778..eb974847 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -171,6 +171,19 @@ This means that public frontends will simply return a `404` as if the share is n The `accounts` table in the database includes a `limitless` column. When this column is set to `true` the account is not subject to any of the limits in the system. +## Experimental Limits Locking + +zrok versions prior to `v0.4.31` had a potential race condition when enforcing resource count limits. This usually only manifested in cases where shares or environments were being allocated programmatically (and fast enough to win the race). + +This was due to a lack of transactional database locking around the limited structures. `v0.4.31` includes a pessimistic locking facility that can be enabled _only_ on the PostgreSQL store implemention. + +If you're running PostgreSQL for your service instance and you want to enable the new experimental locking facility that eliminates the potential resource count race condition, add the `enable_locking: true` flag to your `store` definition: + +```yaml +store: + enable_locking: true +``` + ## Caveats There are a number of caveats that are important to understand when using the limits agent with more complicated limits scenarios: From 53f5efb17c958840ca9e7d92056f908d9ab1521e Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 11 Jun 2024 17:09:46 -0400 Subject: [PATCH 062/117] docs lint (#606) --- .../self-hosting/metrics-and-limits/configuring-limits.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index eb974847..b96a8741 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -173,9 +173,9 @@ The `accounts` table in the database includes a `limitless` column. When this co ## Experimental Limits Locking -zrok versions prior to `v0.4.31` had a potential race condition when enforcing resource count limits. This usually only manifested in cases where shares or environments were being allocated programmatically (and fast enough to win the race). +zrok versions prior to `v0.4.31` had a potential race condition when enforcing resource count limits. This usually only manifested in cases where shares or environments were being allocated programmatically (and fast enough to win the limits race). -This was due to a lack of transactional database locking around the limited structures. `v0.4.31` includes a pessimistic locking facility that can be enabled _only_ on the PostgreSQL store implemention. +This occurs due to a lack of transactional database locking around the limited structures. `v0.4.31` includes a pessimistic locking facility that can be enabled _only_ on the PostgreSQL store implemention. If you're running PostgreSQL for your service instance and you want to enable the new experimental locking facility that eliminates the potential resource count race condition, add the `enable_locking: true` flag to your `store` definition: From 6f35c75203a06ef78d75a81b020a605ecbb20fdd Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 13:21:45 -0400 Subject: [PATCH 063/117] ZROK_CTRL_CONFIG_VERSION override (#648) --- controller/config/config.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/controller/config/config.go b/controller/config/config.go index 8e67f8c0..3a1589d4 100644 --- a/controller/config/config.go +++ b/controller/config/config.go @@ -1,6 +1,8 @@ package config import ( + "os" + "strconv" "time" "github.com/openziti/zrok/controller/emailUi" @@ -14,7 +16,7 @@ import ( "github.com/pkg/errors" ) -const ConfigVersion = 3 +const ConfigVersion = 4 type Config struct { V int @@ -119,8 +121,22 @@ func LoadConfig(path string) (*Config, error) { if err := cf.BindYaml(cfg, path, env.GetCfOptions()); err != nil { return nil, errors.Wrapf(err, "error loading controller config '%v'", path) } - if cfg.V != ConfigVersion { + if !envVersionOk() && cfg.V != ConfigVersion { return nil, errors.Errorf("expecting configuration version '%v', your configuration is version '%v'; please see zrok.io for changelog and configuration documentation", ConfigVersion, cfg.V) } return cfg, nil } + +func envVersionOk() bool { + vStr := os.Getenv("ZROK_CTRL_CONFIG_VERSION") + if vStr != "" { + envV, err := strconv.Atoi(vStr) + if err != nil { + return false + } + if envV == ConfigVersion { + return true + } + } + return false +} From 21a0ddb038603da3cecc358784d91e7bdf8f44a7 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 13:22:21 -0400 Subject: [PATCH 064/117] changelog (#606; #648) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e772864..c98abd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606). This new feature allows for extensive limits customization on a per-user basis, with fallback to the global defaults in the controller configuration. +CHANGE: The controller configuration version has been updated to version `4` (`v: 4`) to support the new limits global configuration changes (https://github.com/openziti/zrok/issues/606). + +CHANGE: A new `ZROK_CTRL_CONFIG_VERSION` environment variable now exists to temporarily force the controller to assume a specific controller configuration version, regardless of what version exists in the file. This allows two different config versions to potentially be co-mingled in the same controller configuration file. Use with care (https://github.com/openziti/zrok/issues/648) + CHANGE: Log messages that said `backend proxy endpoint` were clarified to say `backend target`. FIX: Correct the syntax for the Docker and Linux zrok-share "frontdoor" service that broke OAuth email address pattern matching. From 59ddb4d199a97b1ed619bb4330cf4b94ce17b8fb Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 13:25:13 -0400 Subject: [PATCH 065/117] update reference controller configuration to (#606) --- etc/ctrl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/ctrl.yml b/etc/ctrl.yml index 8fb7d801..0c680b8d 100644 --- a/etc/ctrl.yml +++ b/etc/ctrl.yml @@ -9,7 +9,7 @@ # configuration, the software will expect this field to be incremented. This protects you against invalid configuration # versions and will refer to you to the documentation when the configuration structure changes. # -v: 3 +v: 4 admin: # The `secrets` array contains a list of strings that represent valid `ZROK_ADMIN_TOKEN` values to be used for From 25bd423622775d704deccadcc73b2af957f753b3 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 14:16:25 -0400 Subject: [PATCH 066/117] fix for relax transient cache with multiple accounts (#606) --- controller/limits/agent.go | 49 ++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 7bb8023f..1d7745cc 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -330,13 +330,9 @@ func (a *Agent) relax() error { commit := false if bwjes, err := a.str.FindAllBandwidthLimitJournal(trx); err == nil { - periodBw := make(map[int]struct { - rx int64 - tx int64 - }) - accounts := make(map[int]*store.Account) uls := make(map[int]*userLimits) + accountPeriods := make(map[int]map[int]*periodBwValues) for _, bwje := range bwjes { if _, found := accounts[bwje.AccountId]; !found { @@ -347,7 +343,7 @@ func (a *Agent) relax() error { return errors.Wrapf(err, "error getting user limits for '%v'", acct.Email) } uls[bwje.AccountId] = ul - + accountPeriods[bwje.AccountId] = make(map[int]*periodBwValues) } else { return err } @@ -369,21 +365,20 @@ func (a *Agent) relax() error { bwc = lc } - if _, found := periodBw[bwc.GetPeriodMinutes()]; !found { - rx, tx, err := a.ifx.totalRxTxForAccount(int64(bwje.AccountId), time.Duration(bwc.GetPeriodMinutes())*time.Minute) - if err != nil { - return err - } - periodBw[bwc.GetPeriodMinutes()] = struct { - rx int64 - tx int64 - }{ - rx: rx, - tx: tx, + if periods, accountFound := accountPeriods[bwje.AccountId]; accountFound { + if _, periodFound := periods[bwc.GetPeriodMinutes()]; !periodFound { + rx, tx, err := a.ifx.totalRxTxForAccount(int64(bwje.AccountId), time.Duration(bwc.GetPeriodMinutes())*time.Minute) + if err != nil { + return err + } + periods[bwc.GetPeriodMinutes()] = &periodBwValues{rx: rx, tx: tx} + accountPeriods[bwje.AccountId] = periods } + } else { + return errors.New("accountPeriods corrupted") } - used := periodBw[bwc.GetPeriodMinutes()] + used := accountPeriods[bwje.AccountId][bwc.GetPeriodMinutes()] if !a.transferBytesExceeded(used.rx, used.tx, bwc) { if bwc.GetLimitAction() == store.LimitLimitAction { logrus.Infof("relaxing limit '%v' for '%v'", bwc.String(), accounts[bwje.AccountId].Email) @@ -457,10 +452,7 @@ func (a *Agent) isBandwidthClassLimitedForAccount(acctId int, bwc store.Bandwidt } func (a *Agent) anyBandwidthLimitExceeded(acct *store.Account, u *metrics.Usage, bwcs []store.BandwidthClass) (store.BandwidthClass, int64, int64, error) { - periodBw := make(map[int]struct { - rx int64 - tx int64 - }) + periodBw := make(map[int]periodBwValues) var selectedLc store.BandwidthClass var rxBytes int64 @@ -472,13 +464,7 @@ func (a *Agent) anyBandwidthLimitExceeded(acct *store.Account, u *metrics.Usage, if err != nil { return nil, 0, 0, errors.Wrapf(err, "error getting rx/tx for account '%v'", acct.Email) } - periodBw[bwc.GetPeriodMinutes()] = struct { - rx int64 - tx int64 - }{ - rx: rx, - tx: tx, - } + periodBw[bwc.GetPeriodMinutes()] = periodBwValues{rx: rx, tx: tx} } period := periodBw[bwc.GetPeriodMinutes()] @@ -510,3 +496,8 @@ func (a *Agent) transferBytesExceeded(rx, tx int64, bwc store.BandwidthClass) bo } return false } + +type periodBwValues struct { + rx int64 + tx int64 +} From 33a796daf3015086d06709798721b86bdc5c4ef0 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 14:20:40 -0400 Subject: [PATCH 067/117] docs tweaks (#606) --- .../metrics-and-limits/configuring-limits.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index b96a8741..85f66719 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -14,15 +14,15 @@ If you have not yet configured [metrics](configuring-metrics.md), please visit t ## Understanding the zrok Limits Agent -The limits agent in zrok is used to control the amount of resources that can be consumed by any account in a service instance. +The limits agent in zrok is used to control the amount of resources that can be consumed by any account in a service instance. The limits agent is a component in the zrok controller. -Limits can be specified that control the number of environments, shares, reserved shares, and unique names. Limits that control the number of resources are called _resource count limits_. +Limits can be specified that control the number of environments, shares, reserved shares, and unique names. Limits that control the allowed number of resources are called _resource count limits_. -Limits can be specified that control the amount of data that can be transferred for different types of share backend modes. Limits that control the amount of data that can be transferred are called _bandwidth limits_. +Limits can be specified that control the amount of data that can be transferred within a time period for different types of share backend modes. Limits that control the amount of data that can be transferred are called _bandwidth limits_. -The limits agent in zrok is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured thresholds. +The limits agent in zrok is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured bandwidth thresholds. -zrok limits can be specified _globally_, applying to all users in a service instance. Individual limits can be specified and applied to individual accounts using a new facility called _limit classes_. Limit classes can be used to specify resource count and bandwidth limit defaults per-account. Separate limits for each type share backend can also be specified and applied to user accounts. +zrok limits can be specified _globally_, applying to all users in a service instance. Individual limits can be specified and applied to individual accounts using a facility called _limit classes_. Limit classes can be used to specify resource count and bandwidth limit defaults per-account. Separate limits for each type share backend can also be specified and applied to user accounts. ## The Global Configuration From 425c5ed00c688b2d09fca072c7748c29d1f2b030 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 17:00:36 -0400 Subject: [PATCH 068/117] docs tweaks (#606) --- .../metrics-and-limits/configuring-limits.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 85f66719..88b1cda3 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -14,13 +14,13 @@ If you have not yet configured [metrics](configuring-metrics.md), please visit t ## Understanding the zrok Limits Agent -The limits agent in zrok is used to control the amount of resources that can be consumed by any account in a service instance. The limits agent is a component in the zrok controller. +The limits agent is a component of the zrok controller. -Limits can be specified that control the number of environments, shares, reserved shares, and unique names. Limits that control the allowed number of resources are called _resource count limits_. +The limits agent is responsible for controlling the number of resources in use (environments, shares, etc.) and also for ensuring that accounts are held below the configured bandwidth thresholds. -Limits can be specified that control the amount of data that can be transferred within a time period for different types of share backend modes. Limits that control the amount of data that can be transferred are called _bandwidth limits_. +Limits can be specified that control the number of environments, shares, reserved shares, and unique names that can be created by an account. Limits that control the allowed number of resources are called _resource count limits_. -The limits agent in zrok is responsible for controlling the number of resources in use (environments, shares) and also for ensuring that any single account, environment, or share is held below the configured bandwidth thresholds. +Limits can be specified to control the amount of data that can be transferred within a time period. Limits that control the amount of data that can be transferred are called _bandwidth limits_. zrok limits can be specified _globally_, applying to all users in a service instance. Individual limits can be specified and applied to individual accounts using a facility called _limit classes_. Limit classes can be used to specify resource count and bandwidth limit defaults per-account. Separate limits for each type share backend can also be specified and applied to user accounts. @@ -196,4 +196,8 @@ The zrok limits agent is a work in progress. The system currently does not track There are currently no administrative API endpoints (or corresponding CLI tools) to support creating and applying limit classes in the current release. The limits agent infrastructure was designed to support software integrations that directly manipulate the underlying database structures. -A future release may provide API and CLI tooling to support the human administration of the limits agent. \ No newline at end of file +A future release may provide API and CLI tooling to support the human administration of the limits agent. + +### Performance + +Be sure to minimize the number of different periods used for specifying bandwidth limits. Specifying limits in multiple different periods can cause a multiplicity of queries to be executed against the metrics store (InfluxDB). Standardizing on a period like `24h` or `6h` and using that consistently is the best way to to manage the performance of the metrics store. \ No newline at end of file From eedc0bae4c651630579b702b9a03c6322de8025d Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 17:03:19 -0400 Subject: [PATCH 069/117] docs rev (#606) --- .../self-hosting/metrics-and-limits/configuring-limits.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 88b1cda3..c9bd72fc 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -14,9 +14,11 @@ If you have not yet configured [metrics](configuring-metrics.md), please visit t ## Understanding the zrok Limits Agent -The limits agent is a component of the zrok controller. +The limits agent is a component of the zrok controller. It can be enabled and configured through the zrok controller configuration. -The limits agent is responsible for controlling the number of resources in use (environments, shares, etc.) and also for ensuring that accounts are held below the configured bandwidth thresholds. +The limits agent is responsible for controlling the number of resources in use (environments, shares, etc.) and also for ensuring that accounts are held below the configured data transfer bandwidth thresholds. The limits agent exists to manage resource consumption for larger, multi-user zrok installations. + +### Types of Limits Limits can be specified that control the number of environments, shares, reserved shares, and unique names that can be created by an account. Limits that control the allowed number of resources are called _resource count limits_. From 6c9707eb034fe1ff11edc10962f71348b8b1e6fe Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 17:06:29 -0400 Subject: [PATCH 070/117] docs clarity (#606) --- .../self-hosting/metrics-and-limits/configuring-limits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index c9bd72fc..ec231ef2 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -24,7 +24,7 @@ Limits can be specified that control the number of environments, shares, reserve Limits can be specified to control the amount of data that can be transferred within a time period. Limits that control the amount of data that can be transferred are called _bandwidth limits_. -zrok limits can be specified _globally_, applying to all users in a service instance. Individual limits can be specified and applied to individual accounts using a facility called _limit classes_. Limit classes can be used to specify resource count and bandwidth limit defaults per-account. Separate limits for each type share backend can also be specified and applied to user accounts. +zrok limits can be specified _globally_, applying to all users in a service instance. Limit _classes_ can be created to provide different levels of resource allocation. A single limit class can then be _applied_ to multiple accounts, to alter their limit allocation beyond what's configured in the global configuration. ## The Global Configuration From 74f2fa990d9aca27cdfdb14a7ee4d795e7f6bea0 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 12 Jun 2024 17:23:56 -0400 Subject: [PATCH 071/117] docs (#606) --- .../self-hosting/metrics-and-limits/configuring-limits.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index ec231ef2..b233eb9d 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -24,7 +24,7 @@ Limits can be specified that control the number of environments, shares, reserve Limits can be specified to control the amount of data that can be transferred within a time period. Limits that control the amount of data that can be transferred are called _bandwidth limits_. -zrok limits can be specified _globally_, applying to all users in a service instance. Limit _classes_ can be created to provide different levels of resource allocation. A single limit class can then be _applied_ to multiple accounts, to alter their limit allocation beyond what's configured in the global configuration. +zrok limits can be specified _globally_, applying to all users in a service instance. Limit _classes_ can be created to provide additional levels of resource allocation. Limit classes can then be _applied_ to multiple accounts, to alter their limit allocation beyond what's configured in the global configuration. ## The Global Configuration @@ -72,7 +72,7 @@ These resource counts will be applied to all users in the service instance by de The `bandwidth` section defines the global bandwidth limits for all users in the service instance. -There are two levels of bandwidth limits that can be specified in the global configuration. The first limit defines a _warning_ threshold where the user will receive an email that they are using increased data transfer amounts and will ultimately be subject to a limit. +There are two levels of bandwidth limits that can be specified in the global configuration. The first limit defines a _warning_ threshold where the user will receive an email that they are using increased data transfer amounts and will ultimately be subject to a limit. If you do not want this warning email to be sent, then configure all of the values to `-1` (unlimited). The second limit defines the the actual _limit_ threshold, where the limits agent will disabled traffic for the account's shares. @@ -80,6 +80,8 @@ Bandwidth limits can be specified in terms of `tx` (or _transmitted_ data), `rx` The `period` specifies the time window for the bandwidth limit. See the documentation for [`time.Duration.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for details about the format used for these durations. If the `period` is set to 5 minutes, then the limits agent will monitor the transmitted and receivde traffic for the account for the last 5 minutes, and if the amount of data is greater than either the `warning` or the `limit` threshold, action will be taken. +In the global configuration example above users are allowed to transfer a total of `10485760` bytes in a `5m` period, and they will receive a warning email after they transfer more than `7242880` bytes in a `5m` period. + ## Limit Classes The zrok limits agent includes a concept called _limit classes_. Limit classes can be used to define resource count and bandwidth limits that can be selectively applied to individual accounts in a service instance. From 26147215612293f01ada526fcb83d3a67ec8427c Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 13 Jun 2024 17:42:15 -0400 Subject: [PATCH 072/117] total lint.. --- controller/limits/agent.go | 1 - 1 file changed, 1 deletion(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 1d7745cc..bd14d8af 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -146,7 +146,6 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s logrus.Debugf("account '#%d', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) return false, nil } - logrus.Infof("total = %d", total) } } } From 0b58835aadc0685b01b6703d6a8f5cc8e0e1c93e Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 14 Jun 2024 14:12:54 -0400 Subject: [PATCH 073/117] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c98abd8c..08ed863a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.32 + +FEATURE: Resource count limits now include `frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) + ## v0.4.31 FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606). This new feature allows for extensive limits customization on a per-user basis, with fallback to the global defaults in the controller configuration. From d6025ce1938b9cd8690b5c1d91a821312d831caf Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 15 Jun 2024 00:36:57 +0000 Subject: [PATCH 074/117] fix: upgrade multiple dependencies with Snyk Snyk has created this PR to upgrade: - react from 18.2.0 to 18.3.1. See this package in npm: https://www.npmjs.com/package/react - react-dom from 18.2.0 to 18.3.1. See this package in npm: https://www.npmjs.com/package/react-dom See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/e75726e4-0eab-4078-b930-ddbec091fe6f?utm_source=github&utm_medium=referral&page=upgrade-pr --- ui/package-lock.json | 49 +++++++++++++++++++++++--------------------- ui/package.json | 4 ++-- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index d8b49a7c..3665a2c3 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -18,10 +18,10 @@ "eslint-config-react-app": "^7.0.1", "humanize-duration": "^3.27.3", "moment": "^2.29.4", - "react": "^18.2.0", + "react": "^18.3.1", "react-bootstrap": "^2.7.0", "react-data-table-component": "^7.5.2", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "react-force-graph": "^1.43.0", "react-router-dom": "^6.4.0", "react-sizeme": "^3.0.2", @@ -16061,9 +16061,10 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -16244,15 +16245,16 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-error-overlay": { @@ -17112,9 +17114,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -31435,9 +31438,9 @@ } }, "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "requires": { "loose-envify": "^1.1.0" } @@ -31573,12 +31576,12 @@ } }, "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "requires": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" } }, "react-error-overlay": { @@ -32199,9 +32202,9 @@ } }, "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "requires": { "loose-envify": "^1.1.0" } diff --git a/ui/package.json b/ui/package.json index a585b11d..d3a5d540 100644 --- a/ui/package.json +++ b/ui/package.json @@ -13,10 +13,10 @@ "eslint-config-react-app": "^7.0.1", "humanize-duration": "^3.27.3", "moment": "^2.29.4", - "react": "^18.2.0", + "react": "^18.3.1", "react-bootstrap": "^2.7.0", "react-data-table-component": "^7.5.2", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "react-force-graph": "^1.43.0", "react-router-dom": "^6.4.0", "react-sizeme": "^3.0.2", From 34c9b20ce6fbb288754061ca2ac76808fb1a02b9 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 15 Jun 2024 00:37:01 +0000 Subject: [PATCH 075/117] fix: upgrade react-router-dom from 6.8.0 to 6.23.1 Snyk has created this PR to upgrade react-router-dom from 6.8.0 to 6.23.1. See this package in npm: react-router-dom See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/e75726e4-0eab-4078-b930-ddbec091fe6f?utm_source=github&utm_medium=referral&page=upgrade-pr --- ui/package-lock.json | 59 +++++++++++++++++++++++--------------------- ui/package.json | 2 +- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index d8b49a7c..dd1e335e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -23,7 +23,7 @@ "react-data-table-component": "^7.5.2", "react-dom": "^18.2.0", "react-force-graph": "^1.43.0", - "react-router-dom": "^6.4.0", + "react-router-dom": "^6.23.1", "react-sizeme": "^3.0.2", "recharts": "^2.6.1", "styled-components": "^5.3.5", @@ -3521,11 +3521,12 @@ } }, "node_modules/@remix-run/router": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", - "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=14.0.0" } }, "node_modules/@restart/hooks": { @@ -16324,29 +16325,31 @@ } }, "node_modules/react-router": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", - "integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.3.1" + "@remix-run/router": "1.16.1" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz", - "integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", + "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.3.1", - "react-router": "6.8.0" + "@remix-run/router": "1.16.1", + "react-router": "6.23.1" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8", @@ -22200,9 +22203,9 @@ } }, "@remix-run/router": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.1.tgz", - "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==" + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", + "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==" }, "@restart/hooks": { "version": "0.4.8", @@ -31634,20 +31637,20 @@ } }, "react-router": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", - "integrity": "sha512-760bk7y3QwabduExtudhWbd88IBbuD1YfwzpuDUAlJUJ7laIIcqhMvdhSVh1Fur1PE8cGl84L0dxhR3/gvHF7A==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", + "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", "requires": { - "@remix-run/router": "1.3.1" + "@remix-run/router": "1.16.1" } }, "react-router-dom": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz", - "integrity": "sha512-hQouduSTywGJndE86CXJ2h7YEy4HYC6C/uh19etM+79FfQ6cFFFHnHyDlzO4Pq0eBUI96E4qVE5yUjA00yJZGQ==", + "version": "6.23.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", + "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", "requires": { - "@remix-run/router": "1.3.1", - "react-router": "6.8.0" + "@remix-run/router": "1.16.1", + "react-router": "6.23.1" } }, "react-scripts": { diff --git a/ui/package.json b/ui/package.json index a585b11d..7def9032 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,7 +18,7 @@ "react-data-table-component": "^7.5.2", "react-dom": "^18.2.0", "react-force-graph": "^1.43.0", - "react-router-dom": "^6.4.0", + "react-router-dom": "^6.23.1", "react-sizeme": "^3.0.2", "recharts": "^2.6.1", "styled-components": "^5.3.5", From 5cc4c7a459a4c429e7d1bf48339e3de207c42967 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 15 Jun 2024 00:37:06 +0000 Subject: [PATCH 076/117] fix: upgrade @mui/material from 5.11.7 to 5.15.18 Snyk has created this PR to upgrade @mui/material from 5.11.7 to 5.15.18. See this package in npm: @mui/material See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/e75726e4-0eab-4078-b930-ddbec091fe6f?utm_source=github&utm_medium=referral&page=upgrade-pr --- ui/package-lock.json | 535 +++++++++++++++++++++++++------------------ ui/package.json | 2 +- 2 files changed, 319 insertions(+), 218 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index d8b49a7c..406eb90e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -12,7 +12,7 @@ "@emotion/styled": "^11.10.4", "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.1", - "@mui/material": "^5.10.4", + "@mui/material": "^5.15.18", "bootstrap": "^5.2.3", "dagre": "^0.8.5", "eslint-config-react-app": "^7.0.1", @@ -1758,16 +1758,23 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/@babel/template": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", @@ -2130,17 +2137,24 @@ } }, "node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" } }, + "node_modules/@emotion/cache/node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/@emotion/hash": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", @@ -2155,9 +2169,10 @@ } }, "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" }, "node_modules/@emotion/react": { "version": "11.10.5", @@ -2199,9 +2214,10 @@ } }, "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", + "license": "MIT" }, "node_modules/@emotion/styled": { "version": "11.10.5", @@ -2248,14 +2264,16 @@ } }, "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", + "license": "MIT" }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", @@ -2304,6 +2322,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz", + "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==", + "license": "MIT" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -3166,25 +3222,25 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-alpha.116", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.116.tgz", - "integrity": "sha512-VwhifWdrfHc4/ZdqRZ4Gf+7P39sovNN24By1YVZdvJ9fvp0Sr8sNftGUCjYXXz+xCXVBQDXvhfxMwZrj2MvJvA==", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.7", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -3198,28 +3254,30 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", - "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.20.tgz", + "integrity": "sha512-DoL2ppgldL16utL8nNyj/P12f8mCNdx/Hb/AJnX9rLY4b52hCMIx1kH83pbXQ6uMy6n54M3StmEbvSGoj2OFuA==", + "license": "MIT", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/material": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", - "integrity": "sha512-wDv7Pc6kMe9jeWkmCLt4JChd1lPc2u23JQHpB35L2VwQowpNFoDfIwqi0sYCnZTMKlRc7lza8LqwSwHl2G52Rw==", + "version": "5.15.18", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.18.tgz", + "integrity": "sha512-n+/dsiqux74fFfcRUJjok+ieNQ7+BEk6/OwX9cLcLvriZrZb+/7Y8+Fd2HlUUbn5N0CDurgAHm0VH1DqyJ9HAw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/base": "5.0.0-alpha.116", - "@mui/core-downloads-tracker": "^5.11.7", - "@mui/system": "^5.11.7", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.18", + "@mui/system": "^5.15.15", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -3229,7 +3287,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -3251,12 +3309,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", - "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.20.tgz", + "integrity": "sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/utils": "^5.11.7", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.20", "prop-types": "^15.8.1" }, "engines": { @@ -3264,7 +3323,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -3277,13 +3336,14 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", - "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.6", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -3291,7 +3351,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", @@ -3308,17 +3368,18 @@ } }, "node_modules/@mui/system": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", - "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.20.tgz", + "integrity": "sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/private-theming": "^5.11.7", - "@mui/styled-engine": "^5.11.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.20", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.20", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -3326,7 +3387,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -3347,11 +3408,12 @@ } }, "node_modules/@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", + "license": "MIT", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -3360,13 +3422,13 @@ } }, "node_modules/@mui/utils": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", - "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.20.tgz", + "integrity": "sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.7", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3375,10 +3437,16 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { @@ -3501,9 +3569,10 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4331,9 +4400,10 @@ "dev": true }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "license": "MIT" }, "node_modules/@types/q": { "version": "1.5.5", @@ -4363,18 +4433,11 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -6384,9 +6447,10 @@ } }, "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -7117,9 +7181,10 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/custom-event-polyfill": { "version": "1.0.7", @@ -16639,7 +16704,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.1", @@ -21019,11 +21085,18 @@ } }, "@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } } }, "@babel/template": { @@ -21231,15 +21304,22 @@ } }, "@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", "requires": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + }, + "dependencies": { + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + } } }, "@emotion/hash": { @@ -21256,9 +21336,9 @@ } }, "@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "@emotion/react": { "version": "11.10.5", @@ -21288,9 +21368,9 @@ } }, "@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, "@emotion/styled": { "version": "11.10.5", @@ -21322,14 +21402,14 @@ "requires": {} }, "@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" }, "@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, "@eslint/eslintrc": { "version": "1.4.1", @@ -21362,6 +21442,36 @@ } } }, + "@floating-ui/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "requires": { + "@floating-ui/utils": "^0.2.0" + } + }, + "@floating-ui/dom": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", + "requires": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "@floating-ui/react-dom": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz", + "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==", + "requires": { + "@floating-ui/dom": "^1.0.0" + } + }, + "@floating-ui/utils": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -22022,94 +22132,92 @@ } }, "@mui/base": { - "version": "5.0.0-alpha.116", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.116.tgz", - "integrity": "sha512-VwhifWdrfHc4/ZdqRZ4Gf+7P39sovNN24By1YVZdvJ9fvp0Sr8sNftGUCjYXXz+xCXVBQDXvhfxMwZrj2MvJvA==", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", "requires": { - "@babel/runtime": "^7.20.7", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" } }, "@mui/core-downloads-tracker": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", - "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==" + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.20.tgz", + "integrity": "sha512-DoL2ppgldL16utL8nNyj/P12f8mCNdx/Hb/AJnX9rLY4b52hCMIx1kH83pbXQ6uMy6n54M3StmEbvSGoj2OFuA==" }, "@mui/material": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", - "integrity": "sha512-wDv7Pc6kMe9jeWkmCLt4JChd1lPc2u23JQHpB35L2VwQowpNFoDfIwqi0sYCnZTMKlRc7lza8LqwSwHl2G52Rw==", + "version": "5.15.18", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.18.tgz", + "integrity": "sha512-n+/dsiqux74fFfcRUJjok+ieNQ7+BEk6/OwX9cLcLvriZrZb+/7Y8+Fd2HlUUbn5N0CDurgAHm0VH1DqyJ9HAw==", "requires": { - "@babel/runtime": "^7.20.7", - "@mui/base": "5.0.0-alpha.116", - "@mui/core-downloads-tracker": "^5.11.7", - "@mui/system": "^5.11.7", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.18", + "@mui/system": "^5.15.15", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" } }, "@mui/private-theming": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", - "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.20.tgz", + "integrity": "sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g==", "requires": { - "@babel/runtime": "^7.20.7", - "@mui/utils": "^5.11.7", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.20", "prop-types": "^15.8.1" } }, "@mui/styled-engine": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", - "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", "requires": { - "@babel/runtime": "^7.20.6", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" } }, "@mui/system": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", - "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.20.tgz", + "integrity": "sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA==", "requires": { - "@babel/runtime": "^7.20.7", - "@mui/private-theming": "^5.11.7", - "@mui/styled-engine": "^5.11.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.20", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.20", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" } }, "@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "requires": {} }, "@mui/utils": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", - "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", + "version": "5.15.20", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.20.tgz", + "integrity": "sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==", "requires": { - "@babel/runtime": "^7.20.7", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" } @@ -22187,9 +22295,9 @@ } }, "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@react-aria/ssr": { "version": "3.4.1", @@ -22852,9 +22960,9 @@ "dev": true }, "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "@types/q": { "version": "1.5.5", @@ -22884,18 +22992,10 @@ "csstype": "^3.0.2" } }, - "@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "requires": { - "@types/react": "*" - } - }, "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "requires": { "@types/react": "*" } @@ -24410,9 +24510,9 @@ } }, "clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" }, "co": { "version": "4.6.0", @@ -24947,9 +25047,9 @@ } }, "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "custom-event-polyfill": { "version": "1.0.7", @@ -31885,7 +31985,8 @@ "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "regenerator-transform": { "version": "0.15.1", diff --git a/ui/package.json b/ui/package.json index a585b11d..7e7b5715 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,7 @@ "@emotion/styled": "^11.10.4", "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.1", - "@mui/material": "^5.10.4", + "@mui/material": "^5.15.18", "bootstrap": "^5.2.3", "dagre": "^0.8.5", "eslint-config-react-app": "^7.0.1", From 9f97cdf2798bb856870a3664c8b47b825beb48ea Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 15 Jun 2024 00:37:13 +0000 Subject: [PATCH 077/117] fix: upgrade recharts from 2.6.1 to 2.12.7 Snyk has created this PR to upgrade recharts from 2.6.1 to 2.12.7. See this package in npm: recharts See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/e75726e4-0eab-4078-b930-ddbec091fe6f?utm_source=github&utm_medium=referral&page=upgrade-pr --- ui/package-lock.json | 209 ++++++++++++++----------------------------- ui/package.json | 2 +- 2 files changed, 68 insertions(+), 143 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index d8b49a7c..f4d7cb91 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -25,7 +25,7 @@ "react-force-graph": "^1.43.0", "react-router-dom": "^6.4.0", "react-sizeme": "^3.0.2", - "recharts": "^2.6.1", + "recharts": "^2.12.7", "styled-components": "^5.3.5", "svgo": "^3.0.2" }, @@ -6938,11 +6938,6 @@ "postcss-value-parser": "^4.0.2" } }, - "node_modules/css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" - }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -8941,9 +8936,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } }, "node_modules/fast-glob": { "version": "3.2.12", @@ -16311,18 +16310,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-resize-detector": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", - "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-router": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", @@ -16471,40 +16458,18 @@ } }, "node_modules/react-smooth": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", - "integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "license": "MIT", "dependencies": { - "fast-equals": "^4.0.3", - "react-transition-group": "2.9.0" + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" }, "peerDependencies": { - "prop-types": "^15.6.0", - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-smooth/node_modules/dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "dependencies": { - "@babel/runtime": "^7.1.2" - } - }, - "node_modules/react-smooth/node_modules/react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "dependencies": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-transition-group": { @@ -16558,25 +16523,24 @@ } }, "node_modules/recharts": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.6.1.tgz", - "integrity": "sha512-eGNNqQTSg737HB0tfFkPZbPW8ji7Q8joQM0P2yAEkJkB8CO+LJPgLpx/NUxNHJsxoXvSblMFoy5RSVBYfLU+HA==", + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "license": "MIT", "dependencies": { - "classnames": "^2.2.5", + "clsx": "^2.0.0", "eventemitter3": "^4.0.1", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "react-is": "^16.10.2", - "react-resize-detector": "^8.0.4", - "react-smooth": "^2.0.2", + "react-smooth": "^4.0.0", "recharts-scale": "^0.4.4", - "reduce-css-calc": "^2.1.8", + "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "engines": { - "node": ">=12" + "node": ">=14" }, "peerDependencies": { - "prop-types": "^15.6.0", "react": "^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" } @@ -16589,6 +16553,15 @@ "decimal.js-light": "^2.4.1" } }, + "node_modules/recharts/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/recharts/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -16606,20 +16579,6 @@ "node": ">=6.0.0" } }, - "node_modules/reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "dependencies": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - } - }, - "node_modules/reduce-css-calc/node_modules/postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -18307,6 +18266,12 @@ "node": ">=0.10.0" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinycolor2": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", @@ -24813,11 +24778,6 @@ "postcss-value-parser": "^4.0.2" } }, - "css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" - }, "css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -26324,9 +26284,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-equals": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", - "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==" }, "fast-glob": { "version": "3.2.12", @@ -31625,14 +31585,6 @@ "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "dev": true }, - "react-resize-detector": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", - "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", - "requires": { - "lodash": "^4.17.21" - } - }, "react-router": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", @@ -31744,33 +31696,13 @@ } }, "react-smooth": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz", - "integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", "requires": { - "fast-equals": "^4.0.3", - "react-transition-group": "2.9.0" - }, - "dependencies": { - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "requires": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - } - } + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" } }, "react-transition-group": { @@ -31814,21 +31746,25 @@ } }, "recharts": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.6.1.tgz", - "integrity": "sha512-eGNNqQTSg737HB0tfFkPZbPW8ji7Q8joQM0P2yAEkJkB8CO+LJPgLpx/NUxNHJsxoXvSblMFoy5RSVBYfLU+HA==", + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", "requires": { - "classnames": "^2.2.5", + "clsx": "^2.0.0", "eventemitter3": "^4.0.1", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "react-is": "^16.10.2", - "react-resize-detector": "^8.0.4", - "react-smooth": "^2.0.2", + "react-smooth": "^4.0.0", "recharts-scale": "^0.4.4", - "reduce-css-calc": "^2.1.8", + "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "dependencies": { + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -31853,22 +31789,6 @@ "minimatch": "^3.0.5" } }, - "reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "requires": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -33114,6 +33034,11 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==" }, + "tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "tinycolor2": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", diff --git a/ui/package.json b/ui/package.json index a585b11d..6a07f7a7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -20,7 +20,7 @@ "react-force-graph": "^1.43.0", "react-router-dom": "^6.4.0", "react-sizeme": "^3.0.2", - "recharts": "^2.6.1", + "recharts": "^2.12.7", "styled-components": "^5.3.5", "svgo": "^3.0.2" }, From e8374b361915032357156f2adb33aaa7408b554f Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 15 Jun 2024 00:37:17 +0000 Subject: [PATCH 078/117] fix: upgrade react-bootstrap from 2.7.0 to 2.10.2 Snyk has created this PR to upgrade react-bootstrap from 2.7.0 to 2.10.2. See this package in npm: react-bootstrap See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/e75726e4-0eab-4078-b930-ddbec091fe6f?utm_source=github&utm_medium=referral&page=upgrade-pr --- ui/package-lock.json | 199 ++++++++++++++++++++++++++----------------- ui/package.json | 2 +- 2 files changed, 123 insertions(+), 78 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index d8b49a7c..a96d21bb 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -19,7 +19,7 @@ "humanize-duration": "^3.27.3", "moment": "^2.29.4", "react": "^18.2.0", - "react-bootstrap": "^2.7.0", + "react-bootstrap": "^2.10.2", "react-data-table-component": "^7.5.2", "react-dom": "^18.2.0", "react-force-graph": "^1.43.0", @@ -1758,16 +1758,23 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/@babel/template": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", @@ -3510,11 +3517,15 @@ } }, "node_modules/@react-aria/ssr": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.4.1.tgz", - "integrity": "sha512-NmhoilMDyIfQiOSdQgxpVH2tC2u85Y0mVijtBNbI9kcDYLEiW/r6vKYVKtkyU+C4qobXhGMPfZ70PTc0lysSVA==", + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", + "integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" @@ -3529,29 +3540,31 @@ } }, "node_modules/@restart/hooks": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.8.tgz", - "integrity": "sha512-Ivvp1FZ0Lja80iUTYAhbzy+stxwO7FbPHP95ypCtIh0wyOLiayQywXhVJ2ZYP5S1AjW2GmKHeRU4UglMwTG2sA==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", "dependencies": { - "dequal": "^2.0.2" + "dequal": "^2.0.3" }, "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@restart/ui": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.5.4.tgz", - "integrity": "sha512-ziNtXY2PrjXrRUfR1D/ry1v1i5IF+kfMcH9d1kIcU2lOELfmDsVb+fzbyEDz3yKvKuqkphTunVDuLdYRJ0YsAg==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.9.tgz", + "integrity": "sha512-mUbygUsJcRurjZCt1f77gg4DpheD1D+Sc7J3JjAkysUj7t8m4EBJVOqWC9788Qtbc69cJ+HlJc6jBguKwS8Mcw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.7", + "@babel/runtime": "^7.21.0", "@popperjs/core": "^2.11.6", - "@react-aria/ssr": "^3.4.1", - "@restart/hooks": "^0.4.7", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", "@types/warning": "^3.0.0", "dequal": "^2.0.3", "dom-helpers": "^5.2.0", - "uncontrollable": "^7.2.1", + "uncontrollable": "^8.0.1", "warning": "^4.0.3" }, "peerDependencies": { @@ -3559,6 +3572,15 @@ "react-dom": ">=16.14.0" } }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4038,9 +4060,10 @@ } }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.4.0" } @@ -4372,9 +4395,10 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -4445,9 +4469,10 @@ "dev": true }, "node_modules/@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" }, "node_modules/@types/ws": { "version": "8.5.4", @@ -7553,6 +7578,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -16089,20 +16115,21 @@ } }, "node_modules/react-bootstrap": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.7.0.tgz", - "integrity": "sha512-Jcrn6aUuRVBeSB6dzKODKZU1TONOdhAxu0IDm4Sv74SJUm98dMdhSotF2SNvFEADANoR+stV+7TK6SNX1wWu5w==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.2.tgz", + "integrity": "sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.17.2", - "@restart/hooks": "^0.4.6", - "@restart/ui": "^1.4.1", - "@types/react-transition-group": "^4.4.4", - "classnames": "^2.3.1", + "@babel/runtime": "^7.22.5", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.8", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", "dom-helpers": "^5.2.1", "invariant": "^2.2.4", "prop-types": "^15.8.1", "prop-types-extra": "^1.1.0", - "react-transition-group": "^4.4.2", + "react-transition-group": "^4.4.5", "uncontrollable": "^7.2.1", "warning": "^4.0.3" }, @@ -16639,7 +16666,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.1", @@ -18540,6 +18568,7 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.6.3", "@types/react": ">=16.9.11", @@ -21019,11 +21048,18 @@ } }, "@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } } }, "@babel/template": { @@ -22192,11 +22228,11 @@ "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, "@react-aria/ssr": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.4.1.tgz", - "integrity": "sha512-NmhoilMDyIfQiOSdQgxpVH2tC2u85Y0mVijtBNbI9kcDYLEiW/r6vKYVKtkyU+C4qobXhGMPfZ70PTc0lysSVA==", + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", + "integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==", "requires": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" } }, "@remix-run/router": { @@ -22205,27 +22241,35 @@ "integrity": "sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==" }, "@restart/hooks": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.8.tgz", - "integrity": "sha512-Ivvp1FZ0Lja80iUTYAhbzy+stxwO7FbPHP95ypCtIh0wyOLiayQywXhVJ2ZYP5S1AjW2GmKHeRU4UglMwTG2sA==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", "requires": { - "dequal": "^2.0.2" + "dequal": "^2.0.3" } }, "@restart/ui": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.5.4.tgz", - "integrity": "sha512-ziNtXY2PrjXrRUfR1D/ry1v1i5IF+kfMcH9d1kIcU2lOELfmDsVb+fzbyEDz3yKvKuqkphTunVDuLdYRJ0YsAg==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.9.tgz", + "integrity": "sha512-mUbygUsJcRurjZCt1f77gg4DpheD1D+Sc7J3JjAkysUj7t8m4EBJVOqWC9788Qtbc69cJ+HlJc6jBguKwS8Mcw==", "requires": { - "@babel/runtime": "^7.20.7", + "@babel/runtime": "^7.21.0", "@popperjs/core": "^2.11.6", - "@react-aria/ssr": "^3.4.1", - "@restart/hooks": "^0.4.7", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.4.9", "@types/warning": "^3.0.0", "dequal": "^2.0.3", "dom-helpers": "^5.2.0", - "uncontrollable": "^7.2.1", + "uncontrollable": "^8.0.1", "warning": "^4.0.3" + }, + "dependencies": { + "uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "requires": {} + } } }, "@rollup/plugin-babel": { @@ -22565,9 +22609,9 @@ } }, "@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", "requires": { "tslib": "^2.4.0" } @@ -22893,9 +22937,9 @@ } }, "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "requires": { "@types/react": "*" } @@ -22966,9 +23010,9 @@ "dev": true }, "@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" }, "@types/ws": { "version": "8.5.4", @@ -31457,20 +31501,20 @@ } }, "react-bootstrap": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.7.0.tgz", - "integrity": "sha512-Jcrn6aUuRVBeSB6dzKODKZU1TONOdhAxu0IDm4Sv74SJUm98dMdhSotF2SNvFEADANoR+stV+7TK6SNX1wWu5w==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.2.tgz", + "integrity": "sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA==", "requires": { - "@babel/runtime": "^7.17.2", - "@restart/hooks": "^0.4.6", - "@restart/ui": "^1.4.1", - "@types/react-transition-group": "^4.4.4", - "classnames": "^2.3.1", + "@babel/runtime": "^7.22.5", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.6.8", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", "dom-helpers": "^5.2.1", "invariant": "^2.2.4", "prop-types": "^15.8.1", "prop-types-extra": "^1.1.0", - "react-transition-group": "^4.4.2", + "react-transition-group": "^4.4.5", "uncontrollable": "^7.2.1", "warning": "^4.0.3" } @@ -31885,7 +31929,8 @@ "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "regenerator-transform": { "version": "0.15.1", diff --git a/ui/package.json b/ui/package.json index a585b11d..88fdaae5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,7 +14,7 @@ "humanize-duration": "^3.27.3", "moment": "^2.29.4", "react": "^18.2.0", - "react-bootstrap": "^2.7.0", + "react-bootstrap": "^2.10.2", "react-data-table-component": "^7.5.2", "react-dom": "^18.2.0", "react-force-graph": "^1.43.0", From 69722432787ef9364c7e2a0d7c16269629cd7d25 Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Sat, 15 Jun 2024 16:37:42 -0400 Subject: [PATCH 079/117] Update README.md Signed-off-by: Kenneth Bingham --- docker/compose/zrok-instance/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/compose/zrok-instance/README.md b/docker/compose/zrok-instance/README.md index 1cdf2b02..8d56d167 100644 --- a/docker/compose/zrok-instance/README.md +++ b/docker/compose/zrok-instance/README.md @@ -90,9 +90,9 @@ ZROK_OAUTH_GOOGLE_CLIENT_ID=abcd1234 ZROK_OAUTH_GOOGLE_CLIENT_SECRET=abcd1234 # zrok version, e.g., 1.0.0 -ZROK_IMAGE_TAG=latest +ZROK_CLI_TAG=latest # ziti version, e.g., 1.0.0 -ZITI_IMAGE_TAG=latest +ZITI_CLI_TAG=latest ``` ### Start the Docker Compose Project From 9a55043a81905f1f08158b59a661bb3789f398ae Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Sat, 15 Jun 2024 16:50:00 -0400 Subject: [PATCH 080/117] Update zrok-controller-config.yml.envsubst Set spec v4 Re: https://github.com/openziti/zrok/issues/653 Signed-off-by: Kenneth Bingham --- .../compose/zrok-instance/zrok-controller-config.yml.envsubst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/compose/zrok-instance/zrok-controller-config.yml.envsubst b/docker/compose/zrok-instance/zrok-controller-config.yml.envsubst index 389f4bce..cbdfe639 100644 --- a/docker/compose/zrok-instance/zrok-controller-config.yml.envsubst +++ b/docker/compose/zrok-instance/zrok-controller-config.yml.envsubst @@ -4,7 +4,7 @@ # /___|_| \___/|_|\_\ # controller configuration -v: 3 +v: 4 admin: # generate these admin tokens from a source of randomness, e.g. # LC_ALL=C tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c32 From 7f597ab71431e39ae039604a17315624877304a4 Mon Sep 17 00:00:00 2001 From: Kenneth Bingham Date: Sat, 15 Jun 2024 16:54:01 -0400 Subject: [PATCH 081/117] Update CHANGELOG.md Signed-off-by: Kenneth Bingham --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c98abd8c..d8325861 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.32 + +FIX: use controller config spec v4 in the Docker instance + ## v0.4.31 FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606). This new feature allows for extensive limits customization on a per-user basis, with fallback to the global defaults in the controller configuration. From a1b3b16cffa8cdb9db909d1beca679822723cd28 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 17 Jun 2024 11:32:04 -0400 Subject: [PATCH 082/117] limit_classes added 'frontends' column (#650) --- controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql | 3 +++ controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql create mode 100644 controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql diff --git a/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql b/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql new file mode 100644 index 00000000..e41735aa --- /dev/null +++ b/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter table limit_classes add column frontends int not null default (-1); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql b/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql new file mode 100644 index 00000000..e41735aa --- /dev/null +++ b/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter table limit_classes add column frontends int not null default (-1); \ No newline at end of file From f260449604334b017a44ff6db3ea79cc92d1c014 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 17 Jun 2024 11:41:24 -0400 Subject: [PATCH 083/117] wire 'frontends' through supporting code (#650) --- controller/limits/config.go | 2 ++ controller/limits/resourceCountClass.go | 4 ++++ controller/store/limitClass.go | 13 +++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/controller/limits/config.go b/controller/limits/config.go index 2bc0c8a8..1c9d55ca 100644 --- a/controller/limits/config.go +++ b/controller/limits/config.go @@ -10,6 +10,7 @@ type Config struct { Shares int ReservedShares int UniqueNames int + Frontends int Bandwidth *BandwidthPerPeriod Cycle time.Duration Enforcing bool @@ -49,6 +50,7 @@ func DefaultConfig() *Config { Shares: store.Unlimited, ReservedShares: store.Unlimited, UniqueNames: store.Unlimited, + Frontends: store.Unlimited, Bandwidth: DefaultBandwidthPerPeriod(), Enforcing: false, Cycle: 15 * time.Minute, diff --git a/controller/limits/resourceCountClass.go b/controller/limits/resourceCountClass.go index ca9a6a91..67f0942f 100644 --- a/controller/limits/resourceCountClass.go +++ b/controller/limits/resourceCountClass.go @@ -37,6 +37,10 @@ func (rcc *configResourceCountClass) GetUniqueNames() int { return rcc.cfg.UniqueNames } +func (rcc *configResourceCountClass) GetFrontends() int { + return rcc.cfg.Frontends +} + func (rcc *configResourceCountClass) String() string { return fmt.Sprintf("Config", rcc.cfg.Environments, rcc.cfg.Shares, rcc.cfg.ReservedShares, rcc.cfg.UniqueNames) } diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index a41eb2de..5b9c5dc7 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -22,6 +22,7 @@ type ResourceCountClass interface { GetShares() int GetReservedShares() int GetUniqueNames() int + GetFrontends() int } type BandwidthClass interface { @@ -42,6 +43,7 @@ type LimitClass struct { Shares int ReservedShares int UniqueNames int + Frontends int PeriodMinutes int RxBytes int64 TxBytes int64 @@ -77,6 +79,10 @@ func (lc LimitClass) GetUniqueNames() int { return lc.UniqueNames } +func (lc LimitClass) GetFrontends() int { + return lc.Frontends +} + func (lc LimitClass) GetBackendMode() sdk.BackendMode { if lc.BackendMode == nil { return "" @@ -121,6 +127,9 @@ func (lc LimitClass) String() string { if lc.UniqueNames > Unlimited { out += fmt.Sprintf(", uniqueNames: %d", lc.UniqueNames) } + if lc.Frontends > Unlimited { + out += fmt.Sprintf(", frontends: %d", lc.Frontends) + } if lc.RxBytes > Unlimited || lc.TxBytes > Unlimited || lc.TotalBytes > Unlimited { out += fmt.Sprintf(", periodMinutes: %d", lc.PeriodMinutes) } @@ -140,12 +149,12 @@ func (lc LimitClass) String() string { var _ BandwidthClass = (*LimitClass)(nil) func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into limit_classes (backend_mode, environments, shares, reserved_shares, unique_names, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning id") + stmt, err := trx.Prepare("insert into limit_classes (backend_mode, environments, shares, reserved_shares, unique_names, frontends, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing limit_classes insert statement") } var id int - if err := stmt.QueryRow(lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { + if err := stmt.QueryRow(lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.Frontends, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing limit_classes insert statement") } return id, nil From f174abd18fac34774af3ca7134833da9355a1519 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 17 Jun 2024 12:12:38 -0400 Subject: [PATCH 084/117] frontends -> share_frontends; CanAccessShare refactoring (#650) --- controller/limits/agent.go | 35 ++++++++++++------- controller/limits/config.go | 4 +-- controller/limits/resourceCountClass.go | 4 +-- controller/store/limitClass.go | 16 ++++----- .../postgresql/026_v0_4_32_frontend_count.sql | 2 +- .../sqlite3/026_v0_4_32_frontend_count.sql | 2 +- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index bd14d8af..6e9a8315 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -163,32 +163,43 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { return false, err } if env.AccountId != nil { + if err := a.str.LimitCheckLock(*env.AccountId, trx); err != nil { + return false, err + } + ul, err := a.getUserLimits(*env.AccountId, trx) if err != nil { return false, err } - if ul.resource.IsGlobal() { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(*env.AccountId, trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(*env.AccountId, trx) - if err != nil { - return false, err - } - if lj.Action == store.LimitLimitAction { - return false, nil - } + if scopedBwc, found := ul.scopes[sdk.BackendMode(shr.BackendMode)]; found { + latestScopedJe, err := a.isBandwidthClassLimitedForAccount(*env.AccountId, scopedBwc, trx) + if err != nil { + return false, err + } + if latestScopedJe != nil { + return false, nil } } else { - if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(*env.AccountId, ul.resource.GetLimitClassId(), trx); err == nil && !empty { - lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(*env.AccountId, ul.resource.GetLimitClassId(), trx) + for _, bwc := range ul.bandwidth { + latestJe, err := a.isBandwidthClassLimitedForAccount(*env.AccountId, bwc, trx) if err != nil { return false, err } - if lj.Action == store.LimitLimitAction { + if latestJe != nil { return false, nil } } } + + rc := ul.resource + if scopeRc, found := ul.scopes[sdk.BackendMode(shr.BackendMode)]; found { + rc = scopeRc + } + if rc.GetShareFrontends() > store.Unlimited { + // TODO: Implement frontends+1 check + return true, nil + } } else { return false, nil } diff --git a/controller/limits/config.go b/controller/limits/config.go index 1c9d55ca..c6d69796 100644 --- a/controller/limits/config.go +++ b/controller/limits/config.go @@ -10,7 +10,7 @@ type Config struct { Shares int ReservedShares int UniqueNames int - Frontends int + ShareFrontends int Bandwidth *BandwidthPerPeriod Cycle time.Duration Enforcing bool @@ -50,7 +50,7 @@ func DefaultConfig() *Config { Shares: store.Unlimited, ReservedShares: store.Unlimited, UniqueNames: store.Unlimited, - Frontends: store.Unlimited, + ShareFrontends: store.Unlimited, Bandwidth: DefaultBandwidthPerPeriod(), Enforcing: false, Cycle: 15 * time.Minute, diff --git a/controller/limits/resourceCountClass.go b/controller/limits/resourceCountClass.go index 67f0942f..7d2c4117 100644 --- a/controller/limits/resourceCountClass.go +++ b/controller/limits/resourceCountClass.go @@ -37,8 +37,8 @@ func (rcc *configResourceCountClass) GetUniqueNames() int { return rcc.cfg.UniqueNames } -func (rcc *configResourceCountClass) GetFrontends() int { - return rcc.cfg.Frontends +func (rcc *configResourceCountClass) GetShareFrontends() int { + return rcc.cfg.ShareFrontends } func (rcc *configResourceCountClass) String() string { diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 5b9c5dc7..46df6159 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -22,7 +22,7 @@ type ResourceCountClass interface { GetShares() int GetReservedShares() int GetUniqueNames() int - GetFrontends() int + GetShareFrontends() int } type BandwidthClass interface { @@ -43,7 +43,7 @@ type LimitClass struct { Shares int ReservedShares int UniqueNames int - Frontends int + ShareFrontends int PeriodMinutes int RxBytes int64 TxBytes int64 @@ -79,8 +79,8 @@ func (lc LimitClass) GetUniqueNames() int { return lc.UniqueNames } -func (lc LimitClass) GetFrontends() int { - return lc.Frontends +func (lc LimitClass) GetShareFrontends() int { + return lc.ShareFrontends } func (lc LimitClass) GetBackendMode() sdk.BackendMode { @@ -127,8 +127,8 @@ func (lc LimitClass) String() string { if lc.UniqueNames > Unlimited { out += fmt.Sprintf(", uniqueNames: %d", lc.UniqueNames) } - if lc.Frontends > Unlimited { - out += fmt.Sprintf(", frontends: %d", lc.Frontends) + if lc.ShareFrontends > Unlimited { + out += fmt.Sprintf(", frontends: %d", lc.ShareFrontends) } if lc.RxBytes > Unlimited || lc.TxBytes > Unlimited || lc.TotalBytes > Unlimited { out += fmt.Sprintf(", periodMinutes: %d", lc.PeriodMinutes) @@ -149,12 +149,12 @@ func (lc LimitClass) String() string { var _ BandwidthClass = (*LimitClass)(nil) func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into limit_classes (backend_mode, environments, shares, reserved_shares, unique_names, frontends, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") + stmt, err := trx.Prepare("insert into limit_classes (backend_mode, environments, shares, reserved_shares, unique_names, share_frontends, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing limit_classes insert statement") } var id int - if err := stmt.QueryRow(lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.Frontends, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { + if err := stmt.QueryRow(lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.ShareFrontends, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing limit_classes insert statement") } return id, nil diff --git a/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql b/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql index e41735aa..5de4c3eb 100644 --- a/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql +++ b/controller/store/sql/postgresql/026_v0_4_32_frontend_count.sql @@ -1,3 +1,3 @@ -- +migrate Up -alter table limit_classes add column frontends int not null default (-1); \ No newline at end of file +alter table limit_classes add column share_frontends int not null default (-1); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql b/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql index e41735aa..5de4c3eb 100644 --- a/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql +++ b/controller/store/sql/sqlite3/026_v0_4_32_frontend_count.sql @@ -1,3 +1,3 @@ -- +migrate Up -alter table limit_classes add column frontends int not null default (-1); \ No newline at end of file +alter table limit_classes add column share_frontends int not null default (-1); \ No newline at end of file From 24777a77c8cbf8d63eeaead27ad40bf39d534fce Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 17 Jun 2024 13:16:07 -0400 Subject: [PATCH 085/117] share_frontends implementation and testing (#650) --- controller/limits/agent.go | 10 ++++++++-- controller/limits/resourceCountClass.go | 2 +- controller/limits/userLimits.go | 5 ++--- controller/store/limitClass.go | 2 +- .../metrics-and-limits/configuring-limits.md | 8 +++++--- etc/ctrl.yml | 1 + 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 6e9a8315..78835056 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -197,8 +197,14 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { rc = scopeRc } if rc.GetShareFrontends() > store.Unlimited { - // TODO: Implement frontends+1 check - return true, nil + fes, err := a.str.FindFrontendsForPrivateShare(shr.Id, trx) + if err != nil { + return false, err + } + if len(fes)+1 > rc.GetShareFrontends() { + logrus.Infof("account '#%d' over frontends per share limit '%d'", *env.AccountId, rc.GetReservedShares()) + return false, nil + } } } else { return false, nil diff --git a/controller/limits/resourceCountClass.go b/controller/limits/resourceCountClass.go index 7d2c4117..1f888583 100644 --- a/controller/limits/resourceCountClass.go +++ b/controller/limits/resourceCountClass.go @@ -42,5 +42,5 @@ func (rcc *configResourceCountClass) GetShareFrontends() int { } func (rcc *configResourceCountClass) String() string { - return fmt.Sprintf("Config", rcc.cfg.Environments, rcc.cfg.Shares, rcc.cfg.ReservedShares, rcc.cfg.UniqueNames) + return fmt.Sprintf("Config", rcc.cfg.Environments, rcc.cfg.Shares, rcc.cfg.ReservedShares, rcc.cfg.UniqueNames, rcc.cfg.ShareFrontends) } diff --git a/controller/limits/userLimits.go b/controller/limits/userLimits.go index 3b40e256..5be269a9 100644 --- a/controller/limits/userLimits.go +++ b/controller/limits/userLimits.go @@ -42,7 +42,6 @@ func (ul *userLimits) ignoreBackends(bwc store.BandwidthClass) map[sdk.BackendMo } return ignoreBackends } - return nil } func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) { @@ -85,7 +84,7 @@ func (a *Agent) isResourceCountClass(alc *store.LimitClass) bool { if alc.BackendMode != nil { return false } - if alc.Environments == store.Unlimited && alc.Shares == store.Unlimited && alc.ReservedShares == store.Unlimited && alc.UniqueNames == store.Unlimited { + if alc.Environments == store.Unlimited && alc.Shares == store.Unlimited && alc.ReservedShares == store.Unlimited && alc.UniqueNames == store.Unlimited && alc.ShareFrontends == store.Unlimited { return false } return true @@ -95,7 +94,7 @@ func (a *Agent) isUnscopedBandwidthClass(alc *store.LimitClass) bool { if alc.BackendMode != nil { return false } - if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited { + if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited || alc.ShareFrontends > store.Unlimited { return false } if alc.PeriodMinutes < 1 { diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 46df6159..13558ae4 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -128,7 +128,7 @@ func (lc LimitClass) String() string { out += fmt.Sprintf(", uniqueNames: %d", lc.UniqueNames) } if lc.ShareFrontends > Unlimited { - out += fmt.Sprintf(", frontends: %d", lc.ShareFrontends) + out += fmt.Sprintf(", shareFrontends: %d", lc.ShareFrontends) } if lc.RxBytes > Unlimited || lc.TxBytes > Unlimited || lc.TotalBytes > Unlimited { out += fmt.Sprintf(", periodMinutes: %d", lc.PeriodMinutes) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index b233eb9d..1dde9044 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -20,7 +20,7 @@ The limits agent is responsible for controlling the number of resources in use ( ### Types of Limits -Limits can be specified that control the number of environments, shares, reserved shares, and unique names that can be created by an account. Limits that control the allowed number of resources are called _resource count limits_. +Limits can be specified that control the number of environments, shares, reserved shares, unique names, and frontends per-share that can be created by an account. Limits that control the allowed number of resources are called _resource count limits_. Limits can be specified to control the amount of data that can be transferred within a time period. Limits that control the amount of data that can be transferred are called _bandwidth limits_. @@ -40,6 +40,7 @@ limits: shares: -1 reserved_shares: -1 unique_names: -1 + share_frontends: -1 bandwidth: period: 5m warning: @@ -64,7 +65,7 @@ The `cycle` value controls how frequently the limits agent will evaluate enforce ### Global Resouce Count Limits -The `environments`, `shares`, `reserved_shares`, and `unique_names` specify the resource count limits, globally for the service instance. +The `environments`, `shares`, `reserved_shares`, `unique_names`, and `share_frontends` specify the resource count limits, globally for the service instance. These resource counts will be applied to all users in the service instance by default. @@ -96,6 +97,7 @@ CREATE TABLE public.limit_classes ( shares integer DEFAULT '-1'::integer NOT NULL, reserved_shares integer DEFAULT '-1'::integer NOT NULL, unique_names integer DEFAULT '-1'::integer NOT NULL, + share_frontends integer DEFAULT '-1'::integer NOT NULL, period_minutes integer DEFAULT 1440 NOT NULL, rx_bytes bigint DEFAULT '-1'::integer NOT NULL, tx_bytes bigint DEFAULT '-1'::integer NOT NULL, @@ -130,7 +132,7 @@ Create a row in this table linking the `account_id` to the `limit_class_id` to a To support overriding the resource count limits defined in the global limits configuration, a site administrator can create a limit class by inserting a row into the `limit_classes` table structured like this: ```sql -insert into limit_classes (environments, shares, reserved_shares, unique_names) values (1, 1, 1, 1); +insert into limit_classes (environments, shares, reserved_shares, unique_names, share_frontends) values (1, 1, 1, 1, 1); ``` This creates a limit class that sets the `environments`, `shares`, `reserved_shares`, and `unique_names` all to `1`. diff --git a/etc/ctrl.yml b/etc/ctrl.yml index 0c680b8d..d9b0efd0 100644 --- a/etc/ctrl.yml +++ b/etc/ctrl.yml @@ -83,6 +83,7 @@ limits: shares: -1 reserved_shares: -1 unique_names: -1 + share_frontends: -1 bandwidth: period: 5m warning: From c6bceff10a0d5bf64e28b63586718d2e09377a8b Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Mon, 17 Jun 2024 14:28:39 -0400 Subject: [PATCH 086/117] changelog tweak (#650) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ed863a..a0f094e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.4.32 -FEATURE: Resource count limits now include `frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) +FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) ## v0.4.31 From d22b2925874e3845540e628d90b1578c651bb63d Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 18 Jun 2024 11:02:06 -0400 Subject: [PATCH 087/117] changelog (#539) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1356a9d8..496ec539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## v0.4.32 +FEATURE: New `frontend_grants` table that can be used to control which accounts are allowed to create shares with specific frontend instances (https://github.com/openziti/zrok/issues/539) + FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) FIX: use controller config spec v4 in the Docker instance From c2878dcd8548d8a9e63b1c27966700109a999916 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 18 Jun 2024 11:54:55 -0400 Subject: [PATCH 088/117] frontend_grants for both databases (#539) --- .../postgresql/027_v0_4_32_frontend_grants.sql | 15 +++++++++++++++ .../sql/sqlite3/027_v0_4_32_frontend_grants.sql | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql create mode 100644 controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql diff --git a/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql b/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql new file mode 100644 index 00000000..5b8afc41 --- /dev/null +++ b/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql @@ -0,0 +1,15 @@ +-- +migrate Up + +create table frontend_grants ( + id serial primary key, + + account_id integer references accounts (id) not null, + frontend_id integer references frontends (id) not null, + + created_at timestamptz not null default(current_timestamp), + updated_at timestamptz not null default(current_timestamp), + deleted boolean not null default(false) +); + +create index frontend_grants_account_id_idx on frontend_grants (account_id); +create index frontend_grants_frontend_id_idx on frontend_grants (frontend_id); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql b/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql new file mode 100644 index 00000000..29edee85 --- /dev/null +++ b/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql @@ -0,0 +1,15 @@ +-- +migrate Up + +create table frontend_grants ( + id integer primary key, + + account_id integer references accounts (id) not null, + frontend_id integer references frontends (id) not null, + + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + deleted boolean not null default(false) +); + +create index frontend_grants_account_id_idx on frontend_grants (account_id); +create index frontend_grants_frontend_id_idx on frontend_grants (frontend_id); \ No newline at end of file From 49368dc542151b95e113b180edae63ee68a319e6 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 18 Jun 2024 13:38:00 -0400 Subject: [PATCH 089/117] adjust admin tooling to support creating open/closed permission mode frontends (#539) --- cmd/zrok/adminCreateFrontend.go | 16 +++-- controller/createFrontend.go | 12 ++-- controller/store/frontend.go | 14 ++--- controller/store/share.go | 1 - .../027_v0_4_32_frontend_grants.sql | 2 + .../sqlite3/027_v0_4_32_frontend_grants.sql | 2 + rest_model_zrok/create_frontend_request.go | 58 +++++++++++++++++++ rest_server_zrok/embedded_spec.go | 14 +++++ .../src/zrok/api/.openapi-generator/VERSION | 2 +- .../zrok/api/model/createFrontendRequest.ts | 12 ++++ sdk/nodejs/sdk/src/zrok/api/model/models.ts | 1 + .../sdk/src/zrok/api/model/shareRequest.ts | 3 +- .../models/create_frontend_request.py | 38 +++++++++++- specs/zrok.yml | 3 + ui/src/api/types.js | 1 + 15 files changed, 156 insertions(+), 23 deletions(-) diff --git a/cmd/zrok/adminCreateFrontend.go b/cmd/zrok/adminCreateFrontend.go index 430ecc59..20dc9831 100644 --- a/cmd/zrok/adminCreateFrontend.go +++ b/cmd/zrok/adminCreateFrontend.go @@ -4,6 +4,7 @@ import ( "github.com/openziti/zrok/environment" "github.com/openziti/zrok/rest_client_zrok/admin" "github.com/openziti/zrok/rest_model_zrok" + "github.com/openziti/zrok/sdk/golang/sdk" "github.com/openziti/zrok/tui" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -15,7 +16,8 @@ func init() { } type adminCreateFrontendCommand struct { - cmd *cobra.Command + cmd *cobra.Command + closed bool } func newAdminCreateFrontendCommand() *adminCreateFrontendCommand { @@ -25,6 +27,7 @@ func newAdminCreateFrontendCommand() *adminCreateFrontendCommand { Args: cobra.ExactArgs(3), } command := &adminCreateFrontendCommand{cmd: cmd} + cmd.Flags().BoolVar(&command.closed, "closed", false, "Enabled closed permission mode") cmd.Run = command.run return command } @@ -44,11 +47,16 @@ func (cmd *adminCreateFrontendCommand) run(_ *cobra.Command, args []string) { panic(err) } + permissionMode := sdk.OpenPermissionMode + if cmd.closed { + permissionMode = sdk.ClosedPermissionMode + } req := admin.NewCreateFrontendParams() req.Body = &rest_model_zrok.CreateFrontendRequest{ - ZID: zId, - PublicName: publicName, - URLTemplate: urlTemplate, + ZID: zId, + PublicName: publicName, + URLTemplate: urlTemplate, + PermissionMode: string(permissionMode), } resp, err := zrok.Admin.CreateFrontend(req, mustGetAdminAuth()) diff --git a/controller/createFrontend.go b/controller/createFrontend.go index 45767387..882b753d 100644 --- a/controller/createFrontend.go +++ b/controller/createFrontend.go @@ -2,7 +2,6 @@ package controller import ( "errors" - "github.com/go-openapi/runtime/middleware" "github.com/lib/pq" "github.com/mattn/go-sqlite3" @@ -57,11 +56,12 @@ func (h *createFrontendHandler) Handle(params admin.CreateFrontendParams, princi } fe := &store.Frontend{ - Token: feToken, - ZId: params.Body.ZID, - PublicName: ¶ms.Body.PublicName, - UrlTemplate: ¶ms.Body.URLTemplate, - Reserved: true, + Token: feToken, + ZId: params.Body.ZID, + PublicName: ¶ms.Body.PublicName, + UrlTemplate: ¶ms.Body.URLTemplate, + Reserved: true, + PermissionMode: store.PermissionMode(params.Body.PermissionMode), } if _, err := str.CreateGlobalFrontend(fe, tx); err != nil { perr := &pq.Error{} diff --git a/controller/store/frontend.go b/controller/store/frontend.go index 8d34145e..d4b55faf 100644 --- a/controller/store/frontend.go +++ b/controller/store/frontend.go @@ -14,28 +14,28 @@ type Frontend struct { PublicName *string UrlTemplate *string Reserved bool - Deleted bool + PermissionMode PermissionMode } func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, error) { - stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5, $6, $7) returning id") + stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing frontends insert statement") } var id int - if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil { + if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved, f.PermissionMode).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing frontends insert statement") } return id, nil } func (str *Store) CreateGlobalFrontend(f *Frontend, tx *sqlx.Tx) (int, error) { - stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5) returning id") + stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing global frontends insert statement") } var id int - if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil { + if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved, f.PermissionMode).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing global frontends insert statement") } return id, nil @@ -122,12 +122,12 @@ func (str *Store) FindFrontendsForPrivateShare(shrId int, tx *sqlx.Tx) ([]*Front } func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error { - sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, updated_at = current_timestamp where id = $8" + sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, permission_mode = $8, updated_at = current_timestamp where id = $9" stmt, err := tx.Prepare(sql) if err != nil { return errors.Wrap(err, "error preparing frontends update statement") } - _, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.Id) + _, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.PermissionMode, fe.Id) if err != nil { return errors.Wrap(err, "error executing frontends update statement") } diff --git a/controller/store/share.go b/controller/store/share.go index c12aa529..cd8257e9 100644 --- a/controller/store/share.go +++ b/controller/store/share.go @@ -18,7 +18,6 @@ type Share struct { Reserved bool UniqueName bool PermissionMode PermissionMode - Deleted bool } func (str *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) { diff --git a/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql b/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql index 5b8afc41..ba4c3d51 100644 --- a/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql +++ b/controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql @@ -1,5 +1,7 @@ -- +migrate Up +alter table frontends add column permission_mode permission_mode_type not null default('open'); + create table frontend_grants ( id serial primary key, diff --git a/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql b/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql index 29edee85..1ad99dd3 100644 --- a/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql +++ b/controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql @@ -1,5 +1,7 @@ -- +migrate Up +alter table frontends add column permission_mode string not null default('open'); + create table frontend_grants ( id integer primary key, diff --git a/rest_model_zrok/create_frontend_request.go b/rest_model_zrok/create_frontend_request.go index fdf6b515..183b65ea 100644 --- a/rest_model_zrok/create_frontend_request.go +++ b/rest_model_zrok/create_frontend_request.go @@ -7,9 +7,12 @@ package rest_model_zrok import ( "context" + "encoding/json" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/go-openapi/validate" ) // CreateFrontendRequest create frontend request @@ -17,6 +20,10 @@ import ( // swagger:model createFrontendRequest type CreateFrontendRequest struct { + // permission mode + // Enum: [open closed] + PermissionMode string `json:"permissionMode,omitempty"` + // public name PublicName string `json:"public_name,omitempty"` @@ -29,6 +36,57 @@ type CreateFrontendRequest struct { // Validate validates this create frontend request func (m *CreateFrontendRequest) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePermissionMode(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var createFrontendRequestTypePermissionModePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["open","closed"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + createFrontendRequestTypePermissionModePropEnum = append(createFrontendRequestTypePermissionModePropEnum, v) + } +} + +const ( + + // CreateFrontendRequestPermissionModeOpen captures enum value "open" + CreateFrontendRequestPermissionModeOpen string = "open" + + // CreateFrontendRequestPermissionModeClosed captures enum value "closed" + CreateFrontendRequestPermissionModeClosed string = "closed" +) + +// prop value enum +func (m *CreateFrontendRequest) validatePermissionModeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, createFrontendRequestTypePermissionModePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *CreateFrontendRequest) validatePermissionMode(formats strfmt.Registry) error { + if swag.IsZero(m.PermissionMode) { // not required + return nil + } + + // value enum + if err := m.validatePermissionModeEnum("permissionMode", "body", m.PermissionMode); err != nil { + return err + } + return nil } diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index e74a4198..9afdfdd3 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -1200,6 +1200,13 @@ func init() { "createFrontendRequest": { "type": "object", "properties": { + "permissionMode": { + "type": "string", + "enum": [ + "open", + "closed" + ] + }, "public_name": { "type": "string" }, @@ -2956,6 +2963,13 @@ func init() { "createFrontendRequest": { "type": "object", "properties": { + "permissionMode": { + "type": "string", + "enum": [ + "open", + "closed" + ] + }, "public_name": { "type": "string" }, diff --git a/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/VERSION b/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/VERSION index ba7f754d..93c8ddab 100644 --- a/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/VERSION +++ b/sdk/nodejs/sdk/src/zrok/api/.openapi-generator/VERSION @@ -1 +1 @@ -7.4.0 +7.6.0 diff --git a/sdk/nodejs/sdk/src/zrok/api/model/createFrontendRequest.ts b/sdk/nodejs/sdk/src/zrok/api/model/createFrontendRequest.ts index 75d9842e..35b425ef 100644 --- a/sdk/nodejs/sdk/src/zrok/api/model/createFrontendRequest.ts +++ b/sdk/nodejs/sdk/src/zrok/api/model/createFrontendRequest.ts @@ -16,6 +16,7 @@ export class CreateFrontendRequest { 'zId'?: string; 'urlTemplate'?: string; 'publicName'?: string; + 'permissionMode'?: CreateFrontendRequest.PermissionModeEnum; static discriminator: string | undefined = undefined; @@ -34,6 +35,11 @@ export class CreateFrontendRequest { "name": "publicName", "baseName": "public_name", "type": "string" + }, + { + "name": "permissionMode", + "baseName": "permissionMode", + "type": "CreateFrontendRequest.PermissionModeEnum" } ]; static getAttributeTypeMap() { @@ -41,3 +47,9 @@ export class CreateFrontendRequest { } } +export namespace CreateFrontendRequest { + export enum PermissionModeEnum { + Open = 'open', + Closed = 'closed' + } +} diff --git a/sdk/nodejs/sdk/src/zrok/api/model/models.ts b/sdk/nodejs/sdk/src/zrok/api/model/models.ts index c5fb9183..13a6a25e 100644 --- a/sdk/nodejs/sdk/src/zrok/api/model/models.ts +++ b/sdk/nodejs/sdk/src/zrok/api/model/models.ts @@ -108,6 +108,7 @@ let primitives = [ ]; let enumsMap: {[index: string]: any} = { + "CreateFrontendRequest.PermissionModeEnum": CreateFrontendRequest.PermissionModeEnum, "ShareRequest.ShareModeEnum": ShareRequest.ShareModeEnum, "ShareRequest.BackendModeEnum": ShareRequest.BackendModeEnum, "ShareRequest.OauthProviderEnum": ShareRequest.OauthProviderEnum, diff --git a/sdk/nodejs/sdk/src/zrok/api/model/shareRequest.ts b/sdk/nodejs/sdk/src/zrok/api/model/shareRequest.ts index 83205cb0..086ba7d8 100644 --- a/sdk/nodejs/sdk/src/zrok/api/model/shareRequest.ts +++ b/sdk/nodejs/sdk/src/zrok/api/model/shareRequest.ts @@ -120,7 +120,8 @@ export namespace ShareRequest { UdpTunnel = 'udpTunnel', Caddy = 'caddy', Drive = 'drive', - Socks = 'socks' + Socks = 'socks', + Vpn = 'vpn' } export enum OauthProviderEnum { Github = 'github', diff --git a/sdk/python/sdk/zrok/zrok_api/models/create_frontend_request.py b/sdk/python/sdk/zrok/zrok_api/models/create_frontend_request.py index 946c0ccc..f6a0cf01 100644 --- a/sdk/python/sdk/zrok/zrok_api/models/create_frontend_request.py +++ b/sdk/python/sdk/zrok/zrok_api/models/create_frontend_request.py @@ -30,20 +30,23 @@ class CreateFrontendRequest(object): swagger_types = { 'z_id': 'str', 'url_template': 'str', - 'public_name': 'str' + 'public_name': 'str', + 'permission_mode': 'str' } attribute_map = { 'z_id': 'zId', 'url_template': 'url_template', - 'public_name': 'public_name' + 'public_name': 'public_name', + 'permission_mode': 'permissionMode' } - def __init__(self, z_id=None, url_template=None, public_name=None): # noqa: E501 + def __init__(self, z_id=None, url_template=None, public_name=None, permission_mode=None): # noqa: E501 """CreateFrontendRequest - a model defined in Swagger""" # noqa: E501 self._z_id = None self._url_template = None self._public_name = None + self._permission_mode = None self.discriminator = None if z_id is not None: self.z_id = z_id @@ -51,6 +54,8 @@ class CreateFrontendRequest(object): self.url_template = url_template if public_name is not None: self.public_name = public_name + if permission_mode is not None: + self.permission_mode = permission_mode @property def z_id(self): @@ -115,6 +120,33 @@ class CreateFrontendRequest(object): self._public_name = public_name + @property + def permission_mode(self): + """Gets the permission_mode of this CreateFrontendRequest. # noqa: E501 + + + :return: The permission_mode of this CreateFrontendRequest. # noqa: E501 + :rtype: str + """ + return self._permission_mode + + @permission_mode.setter + def permission_mode(self, permission_mode): + """Sets the permission_mode of this CreateFrontendRequest. + + + :param permission_mode: The permission_mode of this CreateFrontendRequest. # noqa: E501 + :type: str + """ + allowed_values = ["open", "closed"] # noqa: E501 + if permission_mode not in allowed_values: + raise ValueError( + "Invalid value for `permission_mode` ({0}), must be one of {1}" # noqa: E501 + .format(permission_mode, allowed_values) + ) + + self._permission_mode = permission_mode + def to_dict(self): """Returns the model properties as a dict""" result = {} diff --git a/specs/zrok.yml b/specs/zrok.yml index da0a1a36..0f37b6f0 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -766,6 +766,9 @@ definitions: type: string public_name: type: string + permissionMode: + type: string + enum: ["open", "closed"] createFrontendResponse: type: object diff --git a/ui/src/api/types.js b/ui/src/api/types.js index 153ff723..625431a6 100644 --- a/ui/src/api/types.js +++ b/ui/src/api/types.js @@ -53,6 +53,7 @@ * @property {string} zId * @property {string} url_template * @property {string} public_name + * @property {string} permissionMode */ /** From 9bbe4532a0b387cda57d6cd5fe8f8797f2483659 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 18 Jun 2024 13:54:04 -0400 Subject: [PATCH 090/117] add frontend grant check when closed permission mode frontend (#539) --- controller/share.go | 11 +++++++++++ controller/store/frontendGrant.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 controller/store/frontendGrant.go diff --git a/controller/share.go b/controller/share.go index f3a244bf..b5dc286b 100644 --- a/controller/share.go +++ b/controller/share.go @@ -116,6 +116,17 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr logrus.Error(err) return share.NewShareNotFound() } + if sfe.PermissionMode == store.ClosedPermissionMode { + granted, err := str.IsFrontendGrantedToAccount(int(principal.ID), sfe.Id, trx) + if err != nil { + logrus.Error(err) + return share.NewShareInternalServerError() + } + if !granted { + logrus.Errorf("'%v' is not granted access to frontend '%v'", principal.Email, frontendSelection) + return share.NewShareNotFound() + } + } if sfe != nil && sfe.UrlTemplate != nil { frontendZIds = append(frontendZIds, sfe.ZId) frontendTemplates = append(frontendTemplates, *sfe.UrlTemplate) diff --git a/controller/store/frontendGrant.go b/controller/store/frontendGrant.go new file mode 100644 index 00000000..d676653f --- /dev/null +++ b/controller/store/frontendGrant.go @@ -0,0 +1,18 @@ +package store + +import ( + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +func (str *Store) IsFrontendGrantedToAccount(acctId, frontendId int, trx *sqlx.Tx) (bool, error) { + stmt, err := trx.Prepare("select count(0) from frontend_grants where account_id = $1 AND frontend_id = $2") + if err != nil { + return false, errors.Wrap(err, "error preparing frontend_grants select statement") + } + var count int + if err := stmt.QueryRow(acctId, frontendId).Scan(&count); err != nil { + return false, errors.Wrap(err, "error querying frontend_grants count") + } + return count > 0, nil +} From 8683d144cd6a590796fec60d5a4f16eda7441399 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 18 Jun 2024 13:57:50 -0400 Subject: [PATCH 091/117] changelog (#539) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 496ec539..c7951df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.4.32 -FEATURE: New `frontend_grants` table that can be used to control which accounts are allowed to create shares with specific frontend instances (https://github.com/openziti/zrok/issues/539) +FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) From 671d1aa94461ec782262e0fad47c71785cd1690c Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 20 Jun 2024 14:02:58 -0400 Subject: [PATCH 092/117] basic config support for default frontend (#663) --- CHANGELOG.md | 2 ++ cmd/zrok/sharePublic.go | 2 +- environment/env_core/model.go | 4 +++- environment/env_v0_3/api.go | 18 ++++++++++++++++++ environment/env_v0_4/api.go | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7951df0..bf74fb93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) +FEATURE: New config `defaultFrontend` that specifies the default frontend to be used for an environment (https://github.com/openziti/zrok/issues/663) + FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) FIX: use controller config spec v4 in the Docker instance diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index 931365a5..99b7a270 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -45,7 +45,7 @@ func newSharePublicCommand() *sharePublicCommand { Args: cobra.ExactArgs(1), } command := &sharePublicCommand{cmd: cmd} - cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") + cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", []string{"public|"}, "Selected frontends to use for the share") cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy, drive}") cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless") cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for ") diff --git a/environment/env_core/model.go b/environment/env_core/model.go index 8c838429..ebb1df34 100644 --- a/environment/env_core/model.go +++ b/environment/env_core/model.go @@ -13,6 +13,7 @@ type Root interface { Client() (*rest_client_zrok.Zrok, error) ApiEndpoint() (string, string) + DefaultFrontend() (string, string) IsEnabled() bool Environment() *Environment @@ -34,7 +35,8 @@ type Environment struct { } type Config struct { - ApiEndpoint string + ApiEndpoint string + DefaultFrontend string } type Metadata struct { diff --git a/environment/env_v0_3/api.go b/environment/env_v0_3/api.go index b7bd285c..61a03296 100644 --- a/environment/env_v0_3/api.go +++ b/environment/env_v0_3/api.go @@ -85,6 +85,24 @@ func (r *Root) ApiEndpoint() (string, string) { return apiEndpoint, from } +func (r *Root) DefaultFrontend() (string, string) { + defaultFrontend := "public" + from := "binary" + + if r.Config() != nil && r.Config().DefaultFrontend != "" { + defaultFrontend = r.Config().DefaultFrontend + from = "config" + } + + env := os.Getenv("ZROK_DEFAULT_FRONTEND") + if env != "" { + defaultFrontend = env + from = "ZROK_DEFAULT_FRONTEND" + } + + return defaultFrontend, from +} + func (r *Root) Environment() *env_core.Environment { return r.env } diff --git a/environment/env_v0_4/api.go b/environment/env_v0_4/api.go index 3e08202b..35db06c5 100644 --- a/environment/env_v0_4/api.go +++ b/environment/env_v0_4/api.go @@ -85,6 +85,24 @@ func (r *Root) ApiEndpoint() (string, string) { return apiEndpoint, from } +func (r *Root) DefaultFrontend() (string, string) { + defaultFrontend := "public" + from := "binary" + + if r.Config() != nil && r.Config().DefaultFrontend != "" { + defaultFrontend = r.Config().DefaultFrontend + from = "config" + } + + env := os.Getenv("ZROK_DEFAULT_FRONTEND") + if env != "" { + defaultFrontend = env + from = "ZROK_DEFAULT_FRONTEND" + } + + return defaultFrontend, from +} + func (r *Root) Environment() *env_core.Environment { return r.env } From bb8bb0a377095ac72381eae09894f06aec750c97 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 20 Jun 2024 14:31:26 -0400 Subject: [PATCH 093/117] crud for defaultFrontend config (#663) --- cmd/zrok/configGet.go | 6 ++++++ cmd/zrok/configSet.go | 14 ++++++++++++++ cmd/zrok/status.go | 6 ++++-- environment/env_v0_4/root.go | 8 +++++--- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/cmd/zrok/configGet.go b/cmd/zrok/configGet.go index 45480da4..0d1905d0 100644 --- a/cmd/zrok/configGet.go +++ b/cmd/zrok/configGet.go @@ -40,6 +40,12 @@ func (cmd *configGetCommand) run(_ *cobra.Command, args []string) { } else { fmt.Println("apiEndpoint = ") } + case "defaultFrontend": + if env.Config() != nil && env.Config().DefaultFrontend != "" { + fmt.Printf("defaultFrontend = %v\n", env.Config().DefaultFrontend) + } else { + fmt.Println("defaultFrontend = ") + } default: fmt.Printf("unknown config name '%v'\n", configName) } diff --git a/cmd/zrok/configSet.go b/cmd/zrok/configSet.go index 553102e9..44773fbf 100644 --- a/cmd/zrok/configSet.go +++ b/cmd/zrok/configSet.go @@ -63,6 +63,20 @@ func (cmd *configSetCommand) run(_ *cobra.Command, args []string) { fmt.Printf("\n[%v]: because you have a %v-d environment, you won't see your config change until you run %v first!\n\n", tui.WarningLabel, tui.Code.Render("zrok enable"), tui.Code.Render("zrok disable")) } + case "defaultFrontend": + if env.Config() == nil { + if err := env.SetConfig(&env_core.Config{DefaultFrontend: value}); err != nil { + tui.Error("unable to save config", err) + } + } else { + cfg := env.Config() + cfg.DefaultFrontend = value + if err := env.SetConfig(cfg); err != nil { + tui.Error("unable to save config", err) + } + } + fmt.Println("zrok configuration updated") + default: fmt.Printf("unknown config name '%v'\n", configName) os.Exit(1) diff --git a/cmd/zrok/status.go b/cmd/zrok/status.go index fe6d00be..f54f83cb 100644 --- a/cmd/zrok/status.go +++ b/cmd/zrok/status.go @@ -48,8 +48,10 @@ func (cmd *statusCommand) run(_ *cobra.Command, _ []string) { t.SetOutputMirror(os.Stdout) t.SetStyle(table.StyleColoredDark) t.AppendHeader(table.Row{"Config", "Value", "Source"}) - apiEndpoint, from := env.ApiEndpoint() - t.AppendRow(table.Row{"apiEndpoint", apiEndpoint, from}) + apiEndpoint, apiEndpointFrom := env.ApiEndpoint() + t.AppendRow(table.Row{"apiEndpoint", apiEndpoint, apiEndpointFrom}) + defaultFrontend, defaultFrontendFrom := env.DefaultFrontend() + t.AppendRow(table.Row{"defaultFrontend", defaultFrontend, defaultFrontendFrom}) t.Render() _, _ = fmt.Fprintf(os.Stderr, "\n") diff --git a/environment/env_v0_4/root.go b/environment/env_v0_4/root.go index 28804bac..f085f3f6 100644 --- a/environment/env_v0_4/root.go +++ b/environment/env_v0_4/root.go @@ -223,13 +223,14 @@ func loadConfig() (*env_core.Config, error) { return nil, errors.Wrapf(err, "error unmarshaling config file '%v'", cf) } out := &env_core.Config{ - ApiEndpoint: cfg.ApiEndpoint, + ApiEndpoint: cfg.ApiEndpoint, + DefaultFrontend: cfg.DefaultFrontend, } return out, nil } func saveConfig(cfg *env_core.Config) error { - in := &config{ApiEndpoint: cfg.ApiEndpoint} + in := &config{ApiEndpoint: cfg.ApiEndpoint, DefaultFrontend: cfg.DefaultFrontend} data, err := json.MarshalIndent(in, "", " ") if err != nil { return errors.Wrap(err, "error marshaling config") @@ -323,7 +324,8 @@ type metadata struct { } type config struct { - ApiEndpoint string `json:"api_endpoint"` + ApiEndpoint string `json:"api_endpoint"` + DefaultFrontend string `json:"default_frontend"` } type environment struct { From e3dec092849c5133437e663ed03de0b5234a3939 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 20 Jun 2024 15:05:19 -0400 Subject: [PATCH 094/117] better 'zrok config unset' supporting defaultFrontend; also properly extensible (#663) --- cmd/zrok/configUnset.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/cmd/zrok/configUnset.go b/cmd/zrok/configUnset.go index a7cb6930..8060b3bd 100644 --- a/cmd/zrok/configUnset.go +++ b/cmd/zrok/configUnset.go @@ -3,7 +3,6 @@ package main import ( "fmt" "github.com/openziti/zrok/environment" - "github.com/openziti/zrok/environment/env_core" "github.com/openziti/zrok/tui" "github.com/spf13/cobra" "os" @@ -36,18 +35,25 @@ func (cmd *configUnsetCommand) run(_ *cobra.Command, args []string) { panic(err) } - switch configName { - case "apiEndpoint": - if err := env.SetConfig(&env_core.Config{}); err != nil { + if env.Config() != nil { + cfg := env.Config() + switch configName { + case "apiEndpoint": + cfg.ApiEndpoint = "" + if env.IsEnabled() { + fmt.Printf("\n[%v]: because you have a %v-d environment, you won't see your config change until you run %v first!\n\n", tui.WarningLabel, tui.Code.Render("zrok enable"), tui.Code.Render("zrok disable")) + } + + case "defaultFrontend": + cfg.DefaultFrontend = "" + + default: + fmt.Printf("unknown config name '%v'\n", configName) + os.Exit(1) + } + if err := env.SetConfig(cfg); err != nil { tui.Error("unable to save config", err) } fmt.Println("zrok configuration updated") - if env.IsEnabled() { - fmt.Printf("\n[%v]: because you have a %v-d environment, you won't see your config change until you run %v first!\n\n", tui.WarningLabel, tui.Code.Render("zrok enable"), tui.Code.Render("zrok disable")) - } - - default: - fmt.Printf("unknown config name '%v'\n", configName) - os.Exit(1) } } From e82026e0672f64b803114da14f785f35c5377ae0 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 10:20:53 -0400 Subject: [PATCH 095/117] use the new defaultFrontend config in the 'share public' command (#663) --- cmd/zrok/configUnset.go | 2 +- cmd/zrok/sharePublic.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/zrok/configUnset.go b/cmd/zrok/configUnset.go index 8060b3bd..34e5d292 100644 --- a/cmd/zrok/configUnset.go +++ b/cmd/zrok/configUnset.go @@ -46,7 +46,7 @@ func (cmd *configUnsetCommand) run(_ *cobra.Command, args []string) { case "defaultFrontend": cfg.DefaultFrontend = "" - + default: fmt.Printf("unknown config name '%v'\n", configName) os.Exit(1) diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index 99b7a270..d66867df 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -45,7 +45,12 @@ func newSharePublicCommand() *sharePublicCommand { Args: cobra.ExactArgs(1), } command := &sharePublicCommand{cmd: cmd} - cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", []string{"public|"}, "Selected frontends to use for the share") + defaultFrontends := []string{"public"} + if root, err := environment.LoadRoot(); err == nil { + defaultFrontend, _ := root.DefaultFrontend() + defaultFrontends = []string{defaultFrontend} + } + cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", defaultFrontends, "Selected frontends to use for the share") cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy, drive}") cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless") cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for ") From 478c537160a9a552b98b3ef2280a63e816d131a5 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 10:23:59 -0400 Subject: [PATCH 096/117] use defaultFrontend for zrok reserve (#663) --- cmd/zrok/reserve.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index 43890584..8b861738 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -40,8 +40,13 @@ func newReserveCommand() *reserveCommand { Args: cobra.RangeArgs(1, 2), } command := &reserveCommand{cmd: cmd} + defaultFrontends := []string{"public"} + if root, err := environment.LoadRoot(); err == nil { + defaultFrontend, _ := root.DefaultFrontend() + defaultFrontends = []string{defaultFrontend} + } cmd.Flags().StringVarP(&command.uniqueName, "unique-name", "n", "", "A unique name for the reserved share (defaults to generated identifier)") - cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") + cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", defaultFrontends, "Selected frontends to use for the share") cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode (public|private: proxy, web, caddy, drive) (private: tcpTunnel, udpTunnel, socks, vpn)") cmd.Flags().BoolVarP(&command.jsonOutput, "json-output", "j", false, "Emit JSON describing the created reserved share") cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") From 79ab423c1ac75105dda7838010e92b99ee00c875 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 10:27:53 -0400 Subject: [PATCH 097/117] changelog; '--frontend' in 'zrok reserve public' (#663) --- CHANGELOG.md | 4 +++- cmd/zrok/reserve.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf74fb93..7069e15b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,12 @@ FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) -FEATURE: New config `defaultFrontend` that specifies the default frontend to be used for an environment (https://github.com/openziti/zrok/issues/663) +FEATURE: New config `defaultFrontend` that specifies the default frontend to be used for an environment. Provides the default `--frontend` for `zrok share public` and `zrok reserve public` (https://github.com/openziti/zrok/issues/663) FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650) +CHANGE: The frontend selection flag used by `zrok share public` and `zrok reserve public` has been changed from `--frontends` to `--frontend` + FIX: use controller config spec v4 in the Docker instance ## v0.4.31 diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index 8b861738..7aba8f6a 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -46,7 +46,7 @@ func newReserveCommand() *reserveCommand { defaultFrontends = []string{defaultFrontend} } cmd.Flags().StringVarP(&command.uniqueName, "unique-name", "n", "", "A unique name for the reserved share (defaults to generated identifier)") - cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", defaultFrontends, "Selected frontends to use for the share") + cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", defaultFrontends, "Selected frontends to use for the share") cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode (public|private: proxy, web, caddy, drive) (private: tcpTunnel, udpTunnel, socks, vpn)") cmd.Flags().BoolVarP(&command.jsonOutput, "json-output", "j", false, "Emit JSON describing the created reserved share") cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") From 1f0f779d72543e515d859e84393c979696cd6017 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 10:53:18 -0400 Subject: [PATCH 098/117] added a nullable 'label' column to the 'limit_classes' table (#666) --- controller/store/limitClass.go | 12 +++++++++--- controller/store/model.go | 8 -------- .../postgresql/028_v0_4_32_limit_classes_label.sql | 3 +++ .../sql/sqlite3/028_v0_4_32_limit_classes_label.sql | 3 +++ 4 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 controller/store/sql/postgresql/028_v0_4_32_limit_classes_label.sql create mode 100644 controller/store/sql/sqlite3/028_v0_4_32_limit_classes_label.sql diff --git a/controller/store/limitClass.go b/controller/store/limitClass.go index 13558ae4..fbef43e8 100644 --- a/controller/store/limitClass.go +++ b/controller/store/limitClass.go @@ -38,6 +38,7 @@ type BandwidthClass interface { type LimitClass struct { Model + Label *string BackendMode *sdk.BackendMode Environments int Shares int @@ -111,7 +112,12 @@ func (lc LimitClass) GetLimitAction() LimitAction { } func (lc LimitClass) String() string { - out := fmt.Sprintf("LimitClass<#%d", lc.Id) + out := "LimitClass<" + if lc.Label != nil && *lc.Label != "" { + out += "'" + *lc.Label + "'" + } else { + out += fmt.Sprintf("#%d", lc.Id) + } if lc.BackendMode != nil { out += fmt.Sprintf(", backendMode: '%s'", *lc.BackendMode) } @@ -149,12 +155,12 @@ func (lc LimitClass) String() string { var _ BandwidthClass = (*LimitClass)(nil) func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) { - stmt, err := trx.Prepare("insert into limit_classes (backend_mode, environments, shares, reserved_shares, unique_names, share_frontends, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) returning id") + stmt, err := trx.Prepare("insert into limit_classes (label, backend_mode, environments, shares, reserved_shares, unique_names, share_frontends, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) returning id") if err != nil { return 0, errors.Wrap(err, "error preparing limit_classes insert statement") } var id int - if err := stmt.QueryRow(lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.ShareFrontends, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { + if err := stmt.QueryRow(lc.Label, lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.ShareFrontends, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil { return 0, errors.Wrap(err, "error executing limit_classes insert statement") } return id, nil diff --git a/controller/store/model.go b/controller/store/model.go index e4e0d6ab..f4980a1c 100644 --- a/controller/store/model.go +++ b/controller/store/model.go @@ -7,14 +7,6 @@ const ( WarningLimitAction LimitAction = "warning" ) -type LimitScope string - -const ( - AccountLimitScope LimitScope = "account" - EnvironmentLimitScope LimitScope = "environment" - ShareLimitScope LimitScope = "share" -) - type PermissionMode string const ( diff --git a/controller/store/sql/postgresql/028_v0_4_32_limit_classes_label.sql b/controller/store/sql/postgresql/028_v0_4_32_limit_classes_label.sql new file mode 100644 index 00000000..23dd9efb --- /dev/null +++ b/controller/store/sql/postgresql/028_v0_4_32_limit_classes_label.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter table limit_classes add column label varchar(32); \ No newline at end of file diff --git a/controller/store/sql/sqlite3/028_v0_4_32_limit_classes_label.sql b/controller/store/sql/sqlite3/028_v0_4_32_limit_classes_label.sql new file mode 100644 index 00000000..23dd9efb --- /dev/null +++ b/controller/store/sql/sqlite3/028_v0_4_32_limit_classes_label.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter table limit_classes add column label varchar(32); \ No newline at end of file From 3067635615373b6ff6045224c974d0cb4625df0b Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 10:54:44 -0400 Subject: [PATCH 099/117] update docs to include 'label' column (#666) --- .../guides/self-hosting/metrics-and-limits/configuring-limits.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md index 1dde9044..3c3af05c 100644 --- a/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md +++ b/docs/guides/self-hosting/metrics-and-limits/configuring-limits.md @@ -92,6 +92,7 @@ Limit classes are created by creating a record in the `limit_classes` table in t ```sql CREATE TABLE public.limit_classes ( id integer NOT NULL, + label VARCHAR(32), backend_mode public.backend_mode, environments integer DEFAULT '-1'::integer NOT NULL, shares integer DEFAULT '-1'::integer NOT NULL, From 2d5142ac1e6116ace8a8141aaabbe163305138a2 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 15:24:32 -0400 Subject: [PATCH 100/117] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7069e15b..335cb29c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.33 + +CHANGE: Updated react-bootstrap to version 2.10.2. + ## v0.4.32 FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) From 2384dcb95370ca871074842bf086f6dcd25d68bf Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 15:28:58 -0400 Subject: [PATCH 101/117] padding-top warning corrected --- ui/src/console/detail/account/ActionsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/console/detail/account/ActionsTab.js b/ui/src/console/detail/account/ActionsTab.js index 4a3a3605..0b623b92 100644 --- a/ui/src/console/detail/account/ActionsTab.js +++ b/ui/src/console/detail/account/ActionsTab.js @@ -14,7 +14,7 @@ const ActionsTab = (props) => { return (
-
+

Change Password?

Change the password used to log into the zrok web console.

From 759ac77e64a819d4a29edbd8829db945d6be5183 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 15:38:43 -0400 Subject: [PATCH 102/117] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 335cb29c..d5c3c81e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGE: Updated react-bootstrap to version 2.10.2. +CHANGE: Updated @mui/material to version 5.15.18. + ## v0.4.32 FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) From 63aef810d8185c5068afd0e96a1a0b48f6e24e20 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 16:05:13 -0400 Subject: [PATCH 103/117] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c3c81e..b7d4b36e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGE: Updated react-bootstrap to version 2.10.2. CHANGE: Updated @mui/material to version 5.15.18. +CHANGE: Updated react and react-dom to version 18.3.1. + ## v0.4.32 FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) From 00857fe33506aa08d54e4998a463bae6f53cfe61 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 16:11:53 -0400 Subject: [PATCH 104/117] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7d4b36e..44ae99db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ CHANGE: Updated @mui/material to version 5.15.18. CHANGE: Updated react and react-dom to version 18.3.1. +CHANGE: Updated recharts to version 2.12.7. + ## v0.4.32 FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) From 1fde829b67bf8564afa3424cce95d8cf004017dc Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 21 Jun 2024 16:18:17 -0400 Subject: [PATCH 105/117] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ae99db..1dfe470a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ CHANGE: Updated react and react-dom to version 18.3.1. CHANGE: Updated recharts to version 2.12.7. +CHANGE: Updated react-router-dom to version 6.23.1. + ## v0.4.32 FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) From 7ff7fdf536d6c71b17b3baf5b4c65f0d820b6b0b Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 22 Jun 2024 00:46:00 +0000 Subject: [PATCH 106/117] fix: upgrade axios from 1.6.8 to 1.7.2 Snyk has created this PR to upgrade axios from 1.6.8 to 1.7.2. See this package in npm: axios See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/3a48538a-c069-4c5b-904e-084c73ce1ffc?utm_source=github&utm_medium=referral&page=upgrade-pr --- sdk/nodejs/sdk/package-lock.json | 15 ++++++++------- sdk/nodejs/sdk/package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/sdk/nodejs/sdk/package-lock.json b/sdk/nodejs/sdk/package-lock.json index 5116cf37..b83ac276 100644 --- a/sdk/nodejs/sdk/package-lock.json +++ b/sdk/nodejs/sdk/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@openziti/ziti-sdk-nodejs": "^0.16.0", - "axios": "^1.6.8", + "axios": "^1.7.2", "express": "^4.19.2" }, "devDependencies": { @@ -771,9 +771,10 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3754,9 +3755,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/sdk/nodejs/sdk/package.json b/sdk/nodejs/sdk/package.json index 010e0ccc..47c25757 100644 --- a/sdk/nodejs/sdk/package.json +++ b/sdk/nodejs/sdk/package.json @@ -31,7 +31,7 @@ "homepage": "https://github.com/openziti/zrok#readme", "dependencies": { "@openziti/ziti-sdk-nodejs": "^0.16.0", - "axios": "^1.6.8", + "axios": "^1.7.2", "express": "^4.19.2" }, "exports": { From b347f81043ba5768fdcd1a5bda4d31ae8e1c3518 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 22 Jun 2024 00:46:06 +0000 Subject: [PATCH 107/117] fix: upgrade @openziti/ziti-sdk-nodejs from 0.16.0 to 0.17.0 Snyk has created this PR to upgrade @openziti/ziti-sdk-nodejs from 0.16.0 to 0.17.0. See this package in npm: @openziti/ziti-sdk-nodejs See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/3a48538a-c069-4c5b-904e-084c73ce1ffc?utm_source=github&utm_medium=referral&page=upgrade-pr --- sdk/nodejs/sdk/package-lock.json | 15 ++++++++------- sdk/nodejs/sdk/package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/sdk/nodejs/sdk/package-lock.json b/sdk/nodejs/sdk/package-lock.json index 5116cf37..74248e11 100644 --- a/sdk/nodejs/sdk/package-lock.json +++ b/sdk/nodejs/sdk/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@openziti/ziti-sdk-nodejs": "^0.16.0", + "@openziti/ziti-sdk-nodejs": "^0.17.0", "axios": "^1.6.8", "express": "^4.19.2" }, @@ -498,10 +498,11 @@ } }, "node_modules/@openziti/ziti-sdk-nodejs": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@openziti/ziti-sdk-nodejs/-/ziti-sdk-nodejs-0.16.0.tgz", - "integrity": "sha512-jQG5Yn6XojfGXkVHliZReY48bq7P2fFWzyOtXw37GdTeo+RQRl9YS57ieRF70NrlL0oEkO1/84wSQBfpX+uj+A==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@openziti/ziti-sdk-nodejs/-/ziti-sdk-nodejs-0.17.0.tgz", + "integrity": "sha512-eufD2LxhRfB8yPUkUFStFJN4GAmLM8u2m0BKAwOdcYy7KTrgWpiDhE/tt2orCtTdd7F+opUSA590ubY48g9RNQ==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@mapbox/node-pre-gyp": "^1.0.11", "bindings": "^1.5.0", @@ -3518,9 +3519,9 @@ } }, "@openziti/ziti-sdk-nodejs": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@openziti/ziti-sdk-nodejs/-/ziti-sdk-nodejs-0.16.0.tgz", - "integrity": "sha512-jQG5Yn6XojfGXkVHliZReY48bq7P2fFWzyOtXw37GdTeo+RQRl9YS57ieRF70NrlL0oEkO1/84wSQBfpX+uj+A==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@openziti/ziti-sdk-nodejs/-/ziti-sdk-nodejs-0.17.0.tgz", + "integrity": "sha512-eufD2LxhRfB8yPUkUFStFJN4GAmLM8u2m0BKAwOdcYy7KTrgWpiDhE/tt2orCtTdd7F+opUSA590ubY48g9RNQ==", "requires": { "@mapbox/node-pre-gyp": "^1.0.11", "bindings": "^1.5.0", diff --git a/sdk/nodejs/sdk/package.json b/sdk/nodejs/sdk/package.json index 010e0ccc..410ad94d 100644 --- a/sdk/nodejs/sdk/package.json +++ b/sdk/nodejs/sdk/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/openziti/zrok#readme", "dependencies": { - "@openziti/ziti-sdk-nodejs": "^0.16.0", + "@openziti/ziti-sdk-nodejs": "^0.17.0", "axios": "^1.6.8", "express": "^4.19.2" }, From d10c3019b6ff7dada552a0030d2999873bf4cb72 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 22 Jun 2024 01:24:17 +0000 Subject: [PATCH 108/117] fix: upgrade multiple dependencies with Snyk Snyk has created this PR to upgrade: - react from 18.2.0 to 18.3.1. See this package in npm: https://www.npmjs.com/package/react - react-dom from 18.2.0 to 18.3.1. See this package in npm: https://www.npmjs.com/package/react-dom See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/5ffefd8e-f752-4ba8-8e82-300b94419264?utm_source=github&utm_medium=referral&page=upgrade-pr --- website/package-lock.json | 29 ++++++++++++++++------------- website/package.json | 4 ++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 7ff24670..fa317d51 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14,9 +14,9 @@ "@mdx-js/react": "^3.0.0", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", - "react": "^18.2.0", + "react": "^18.3.1", "react-device-detect": "^2.2.3", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "remark-math": "^5.1.1" }, "devDependencies": { @@ -12033,9 +12033,10 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -12174,15 +12175,16 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-error-overlay": { @@ -12948,9 +12950,10 @@ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } diff --git a/website/package.json b/website/package.json index 990feab7..1c54dac6 100644 --- a/website/package.json +++ b/website/package.json @@ -20,9 +20,9 @@ "@mdx-js/react": "^3.0.0", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", - "react": "^18.2.0", + "react": "^18.3.1", "react-device-detect": "^2.2.3", - "react-dom": "^18.2.0", + "react-dom": "^18.3.1", "remark-math": "^5.1.1" }, "devDependencies": { From c3bceab6bdd46fbcb5c7dc6414447d4bab974c85 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 22 Jun 2024 01:24:21 +0000 Subject: [PATCH 109/117] fix: upgrade @mdx-js/react from 3.0.0 to 3.0.1 Snyk has created this PR to upgrade @mdx-js/react from 3.0.0 to 3.0.1. See this package in npm: @mdx-js/react See this project in Snyk: https://app.snyk.io/org/mike.gorman/project/5ffefd8e-f752-4ba8-8e82-300b94419264?utm_source=github&utm_medium=referral&page=upgrade-pr --- website/package-lock.json | 9 +++++---- website/package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 7ff24670..5dc7dfe0 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,7 +11,7 @@ "@docusaurus/core": "^3.3.2", "@docusaurus/plugin-client-redirects": "^3.3.2", "@docusaurus/preset-classic": "^3.3.2", - "@mdx-js/react": "^3.0.0", + "@mdx-js/react": "^3.0.1", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", @@ -2962,9 +2962,10 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.0.tgz", - "integrity": "sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", + "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", + "license": "MIT", "dependencies": { "@types/mdx": "^2.0.0" }, diff --git a/website/package.json b/website/package.json index 990feab7..6cada802 100644 --- a/website/package.json +++ b/website/package.json @@ -17,7 +17,7 @@ "@docusaurus/core": "^3.3.2", "@docusaurus/plugin-client-redirects": "^3.3.2", "@docusaurus/preset-classic": "^3.3.2", - "@mdx-js/react": "^3.0.0", + "@mdx-js/react": "^3.0.1", "clsx": "^1.2.1", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", From 3464f81b5f52f13773ab1ba66e9d37e8a549fb9f Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 13:28:18 -0400 Subject: [PATCH 110/117] changelog --- CHANGELOG.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dfe470a..b061d356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,17 @@ ## v0.4.33 -CHANGE: Updated react-bootstrap to version 2.10.2. +CHANGE: Updated `react-bootstrap` to version `2.10.2` (web console). -CHANGE: Updated @mui/material to version 5.15.18. +CHANGE: Updated `@mui/material` to version `5.15.18` (web console). -CHANGE: Updated react and react-dom to version 18.3.1. +CHANGE: Updated `react` and `react-dom` to version `18.3.1` (web console). -CHANGE: Updated recharts to version 2.12.7. +CHANGE: Updated `recharts` to version `2.12.7` (web console). -CHANGE: Updated react-router-dom to version 6.23.1. +CHANGE: Updated `react-router-dom` to version `6.23.1` (web console). + +CHANGE: Updated `axios` to version `1.7.2` for (node SDK). ## v0.4.32 From 6ec2fa1f6e11ff4d2e271752d66ed729d0c9cab6 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 13:37:53 -0400 Subject: [PATCH 111/117] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b061d356..7a0f10ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ CHANGE: Updated `react-router-dom` to version `6.23.1` (web console). CHANGE: Updated `axios` to version `1.7.2` for (node SDK). +CHANGE: Updated `@openziti/ziti-sdk-nodejs` to version `0.17.0` (node SDK). + ## v0.4.32 FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539) From 0ffb3bd5beea9a08e6217e1d1d564c4c1aa5cb5e Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 14:09:50 -0400 Subject: [PATCH 112/117] fix for bad log message in Agent.CanAccessShare --- controller/limits/agent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index 78835056..b9403063 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -202,7 +202,7 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) { return false, err } if len(fes)+1 > rc.GetShareFrontends() { - logrus.Infof("account '#%d' over frontends per share limit '%d'", *env.AccountId, rc.GetReservedShares()) + logrus.Infof("account '#%d' over frontends per share limit '%d'", *env.AccountId, rc.GetShareFrontends()) return false, nil } } From 01f98ed08a5568033a2fd18220161940eb6d0891 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 14:20:06 -0400 Subject: [PATCH 113/117] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a0f10ea..5e4dcfcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## v0.4.33 +FIX: Fix for log message in `Agent.CanAccessShare` (`"account '#%d' over frontends per share limit '%d'"`), which was not returning the correct limit value. + CHANGE: Updated `react-bootstrap` to version `2.10.2` (web console). CHANGE: Updated `@mui/material` to version `5.15.18` (web console). From f68cb7146612a1041aee5540307ec42450e817f3 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 14:27:45 -0400 Subject: [PATCH 114/117] fix for private frontend permission mode (#677) --- controller/access.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/access.go b/controller/access.go index 5b9d8dce..e3fc9d9a 100644 --- a/controller/access.go +++ b/controller/access.go @@ -81,7 +81,7 @@ func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_ return share.NewAccessInternalServerError() } - if _, err := str.CreateFrontend(envId, &store.Frontend{PrivateShareId: &shr.Id, Token: feToken, ZId: envZId}, trx); err != nil { + if _, err := str.CreateFrontend(envId, &store.Frontend{PrivateShareId: &shr.Id, Token: feToken, ZId: envZId, PermissionMode: store.ClosedPermissionMode}, trx); err != nil { logrus.Errorf("error creating frontend record for user '%v': %v", principal.Email, err) return share.NewAccessInternalServerError() } From a2425f77dc565a87bde9cf14276c40348605973f Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 14:28:56 -0400 Subject: [PATCH 115/117] changelog (#677) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e4dcfcd..f07411d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ FIX: Fix for log message in `Agent.CanAccessShare` (`"account '#%d' over frontends per share limit '%d'"`), which was not returning the correct limit value. +FIX: Properly set `permission_mode` in `frontends` when createing a private frontend using `zrok access private` (https://github.com/openziti/zrok/issues/677) + CHANGE: Updated `react-bootstrap` to version `2.10.2` (web console). CHANGE: Updated `@mui/material` to version `5.15.18` (web console). From 466ae110d21d04b00915bc423bab8777581c45b3 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 16:09:51 -0400 Subject: [PATCH 116/117] fix for mixing limited and unlimited resource counts (#680) --- controller/limits/agent.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/limits/agent.go b/controller/limits/agent.go index b9403063..45c27a93 100644 --- a/controller/limits/agent.go +++ b/controller/limits/agent.go @@ -134,15 +134,15 @@ func (a *Agent) CanCreateShare(acctId, envId int, reserved, uniqueName bool, _ s uniqueNames++ } } - if total+1 > rc.GetShares() { + if rc.GetShares() > store.Unlimited && total+1 > rc.GetShares() { logrus.Debugf("account '#%d', environment '%d' over shares limit '%d'", acctId, envId, a.cfg.Shares) return false, nil } - if reserved && reserveds+1 > rc.GetReservedShares() { + if reserved && rc.GetReservedShares() > store.Unlimited && reserveds+1 > rc.GetReservedShares() { logrus.Debugf("account '#%d', environment '%d' over reserved shares limit '%d'", acctId, envId, a.cfg.ReservedShares) return false, nil } - if reserved && uniqueName && uniqueNames+1 > rc.GetUniqueNames() { + if reserved && uniqueName && rc.GetUniqueNames() > store.Unlimited && uniqueNames+1 > rc.GetUniqueNames() { logrus.Debugf("account '#%d', environment '%d' over unique names limit '%d'", acctId, envId, a.cfg.UniqueNames) return false, nil } From 2a98a025876f0b5ed63a4e36bae9e1bbaf378414 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Tue, 25 Jun 2024 16:10:54 -0400 Subject: [PATCH 117/117] changelog (#680) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f07411d9..b46c554d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v0.4.34 + +FIX: Fix for mixing limited and unlimited (-1) resource counts in the limits system (https://github.com/openziti/zrok/issues/680) + ## v0.4.33 FIX: Fix for log message in `Agent.CanAccessShare` (`"account '#%d' over frontends per share limit '%d'"`), which was not returning the correct limit value.