From b6114c26daf1e394320ad423be358755a4d44f64 Mon Sep 17 00:00:00 2001 From: kyma-btp-manager-serviceuser Date: Wed, 6 Mar 2024 11:56:53 +0100 Subject: [PATCH 1/3] initial commit but compilation fails correct db setup creation upsert redefined unit tests for db passing unit test for db layer all functions covered in-memory covered --- go.mod | 7 ++ go.sum | 5 + internal/model.go | 8 ++ internal/storage/dbmodel/subaccount_state.go | 10 ++ internal/storage/driver/memory/instance.go | 14 +++ .../storage/driver/memory/subaccount_state.go | 50 +++++++++ .../driver/memory/subaccount_state_test.go | 92 ++++++++++++++++ internal/storage/driver/postsql/instance.go | 21 ++++ .../storage/driver/postsql/instance_test.go | 34 ++++++ .../driver/postsql/subaccount_state.go | 100 ++++++++++++++++++ .../driver/postsql/subaccount_state_test.go | 100 ++++++++++++++++++ internal/storage/ext.go | 7 ++ internal/storage/postsql/factory.go | 4 + internal/storage/postsql/init.go | 3 +- internal/storage/postsql/read.go | 29 +++++ internal/storage/postsql/write.go | 35 ++++++ internal/storage/storage.go | 8 ++ 17 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 internal/storage/dbmodel/subaccount_state.go create mode 100644 internal/storage/driver/memory/subaccount_state.go create mode 100644 internal/storage/driver/memory/subaccount_state_test.go create mode 100644 internal/storage/driver/postsql/subaccount_state.go create mode 100644 internal/storage/driver/postsql/subaccount_state_test.go diff --git a/go.mod b/go.mod index 3c38f93509..9e281251a0 100644 --- a/go.mod +++ b/go.mod @@ -72,8 +72,10 @@ require ( github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/swag v0.22.9 // indirect @@ -81,6 +83,7 @@ require ( github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.7.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect @@ -126,6 +129,8 @@ require ( go.opentelemetry.io/otel/sdk v1.23.1 // indirect go.opentelemetry.io/otel/trace v1.23.1 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect @@ -133,10 +138,12 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gotest.tools/v3 v3.1.0 // indirect + k8s.io/component-base v0.26.3 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240221221325-2ac9dc51f3f1 // indirect k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect diff --git a/go.sum b/go.sum index c969bf89e5..48b66e5a9a 100644 --- a/go.sum +++ b/go.sum @@ -726,6 +726,7 @@ github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= @@ -929,6 +930,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= @@ -1176,6 +1178,8 @@ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7e go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -1439,6 +1443,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/model.go b/internal/model.go index 044aac7486..87a2b65dae 100644 --- a/internal/model.go +++ b/internal/model.go @@ -714,3 +714,11 @@ func (c *ConfigForPlan) ContainsAdditionalComponent(componentName string) bool { } return false } + +type SubaccountState struct { + ID string `json:"id"` + + BetaEnabled string `json:"betaEnabled"` + UsedForProduction string `json:"usedForProduction"` + ModifiedAt int64 `json:"modifiedAt"` +} diff --git a/internal/storage/dbmodel/subaccount_state.go b/internal/storage/dbmodel/subaccount_state.go new file mode 100644 index 0000000000..73b5fbd171 --- /dev/null +++ b/internal/storage/dbmodel/subaccount_state.go @@ -0,0 +1,10 @@ +package dbmodel + +type SubaccountStateDTO struct { + ID string `json:"id"` + + BetaEnabled string `json:"beta_enabled"` + UsedForProduction string `json:"used_for_production"` + + ModifiedAt int64 `json:"modified_at_at"` +} diff --git a/internal/storage/driver/memory/instance.go b/internal/storage/driver/memory/instance.go index a87a0858a4..f340620efd 100644 --- a/internal/storage/driver/memory/instance.go +++ b/internal/storage/driver/memory/instance.go @@ -29,6 +29,20 @@ func NewInstance(operations *operations) *instances { } } +func (s *instances) GetDistinctSubAccounts() ([]string, error) { + //iterate over instances and return distinct subaccounts + collectedSubAccounts := make(map[string]struct{}) + for _, v := range s.instances { + collectedSubAccounts[v.SubAccountID] = struct{}{} + } + //convert map keys to slice + var subAccounts []string + for k := range collectedSubAccounts { + subAccounts = append(subAccounts, k) + } + return subAccounts, nil +} + func (s *instances) InsertWithoutEncryption(instance internal.Instance) error { return errors.New("not implemented") } diff --git a/internal/storage/driver/memory/subaccount_state.go b/internal/storage/driver/memory/subaccount_state.go new file mode 100644 index 0000000000..d4ce58d7fc --- /dev/null +++ b/internal/storage/driver/memory/subaccount_state.go @@ -0,0 +1,50 @@ +package memory + +import ( + "sync" + + "github.com/kyma-project/kyma-environment-broker/internal" +) + +type SubaccountStates struct { + mutex sync.Mutex + + subaccountStates map[string]internal.SubaccountState +} + +func NewSubaccountStates() *SubaccountStates { + return &SubaccountStates{ + subaccountStates: make(map[string]internal.SubaccountState, 0), + } +} + +func (s *SubaccountStates) DeleteState(subaccountID string) error { + s.mutex.Lock() + defer s.mutex.Unlock() + delete(s.subaccountStates, subaccountID) + + return nil +} + +func (s *SubaccountStates) UpsertState(subaccountState internal.SubaccountState) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.subaccountStates[subaccountState.ID] = subaccountState + + return nil +} + +func (s *SubaccountStates) ListStates() ([]internal.SubaccountState, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + var states []internal.SubaccountState + + //convert map to slice + for _, state := range s.subaccountStates { + states = append(states, state) + } + + return states, nil +} diff --git a/internal/storage/driver/memory/subaccount_state_test.go b/internal/storage/driver/memory/subaccount_state_test.go new file mode 100644 index 0000000000..6bb9b9df3a --- /dev/null +++ b/internal/storage/driver/memory/subaccount_state_test.go @@ -0,0 +1,92 @@ +package memory + +import ( + "github.com/kyma-project/kyma-environment-broker/internal" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + subaccountID1 = "subaccountID1" + subaccountID2 = "subaccountID2" +) + +var ( + givenSubaccount1State1 = internal.SubaccountState{ + ID: subaccountID1, + BetaEnabled: "true", + UsedForProduction: "NOT_SET", + ModifiedAt: 10, + } + + givenSubaccount1State2 = internal.SubaccountState{ + ID: subaccountID1, + BetaEnabled: "false", + UsedForProduction: "NOT_SET", + ModifiedAt: 54, + } + givenSubaccount2State3 = internal.SubaccountState{ + ID: subaccountID2, + BetaEnabled: "true", + UsedForProduction: "USED_FOR_PRODUCTION", + ModifiedAt: 108, + } +) + +func TestSubaccountState(t *testing.T) { + subaccountStates := NewSubaccountStates() + + t.Run("should insert and fetch state", func(t *testing.T) { + + err := subaccountStates.UpsertState(givenSubaccount1State1) + require.NoError(t, err) + + actualStates, err := subaccountStates.ListStates() + require.NoError(t, err) + assert.Len(t, actualStates, 1) + assert.Equal(t, "true", actualStates[0].BetaEnabled) + assert.Equal(t, "NOT_SET", actualStates[0].UsedForProduction) + }) + + t.Run("should update subaccount state and then fetch it", func(t *testing.T) { + + err := subaccountStates.UpsertState(givenSubaccount1State2) + + require.NoError(t, err) + actualStates, err := subaccountStates.ListStates() + require.NoError(t, err) + assert.Len(t, actualStates, 1) + assert.Equal(t, "false", actualStates[0].BetaEnabled) + assert.Equal(t, "NOT_SET", actualStates[0].UsedForProduction) + }) + + t.Run("insert second subaccount state and fetch two states", func(t *testing.T) { + + err := subaccountStates.UpsertState(givenSubaccount2State3) + + require.NoError(t, err) + actualStates, err := subaccountStates.ListStates() + require.NoError(t, err) + assert.Len(t, actualStates, 2) + expectedStates := []internal.SubaccountState{givenSubaccount1State2, givenSubaccount2State3} + + for _, expectedState := range expectedStates { + assert.Contains(t, actualStates, expectedState) + + } + }) + + t.Run("delete second subaccount state and fetch one state", func(t *testing.T) { + + err := subaccountStates.DeleteState(subaccountID2) + + require.NoError(t, err) + actualStates, err := subaccountStates.ListStates() + require.NoError(t, err) + assert.Equal(t, givenSubaccount1State2, actualStates[0]) + assert.Len(t, actualStates, 1) + }) + +} diff --git a/internal/storage/driver/postsql/instance.go b/internal/storage/driver/postsql/instance.go index 2ba5e83efa..5057f7f9c8 100644 --- a/internal/storage/driver/postsql/instance.go +++ b/internal/storage/driver/postsql/instance.go @@ -22,6 +22,27 @@ type Instance struct { cipher Cipher } +func (s *Instance) GetDistinctSubAccounts() ([]string, error) { + sess := s.NewReadSession() + var ( + subAccounts []string + lastErr dberr.Error + ) + err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) { + subAccounts, lastErr = sess.GetDistinctSubAccounts() + if lastErr != nil { + log.Errorf("while fetching distinct subaccounts list: %v", lastErr) + return false, nil + } + return true, nil + }) + if err != nil { + return nil, lastErr + } + + return subAccounts, nil +} + func NewInstance(sess postsql.Factory, operations *operations, cipher Cipher) *Instance { return &Instance{ Factory: sess, diff --git a/internal/storage/driver/postsql/instance_test.go b/internal/storage/driver/postsql/instance_test.go index 24054365ee..c7c2aff292 100644 --- a/internal/storage/driver/postsql/instance_test.go +++ b/internal/storage/driver/postsql/instance_test.go @@ -158,6 +158,39 @@ func TestInstance(t *testing.T) { assert.Equal(t, 0, numberOfInstancesB) }) + t.Run("Should get distinct subaccounts from active instances", func(t *testing.T) { + storageCleanup, brokerStorage, err := GetStorageForDatabaseTests() + require.NoError(t, err) + require.NotNil(t, brokerStorage) + defer func() { + err := storageCleanup() + assert.NoError(t, err) + }() + + // populate database with samples + fixInstances := []internal.Instance{ + *fixInstance(instanceData{val: "A1", globalAccountID: "ga1", subAccountID: "sa1", runtimeID: "runtimeID1"}), + *fixInstance(instanceData{val: "A2", globalAccountID: "ga1", subAccountID: "sa1", runtimeID: "runtimeID2"}), + *fixInstance(instanceData{val: "A3", globalAccountID: "ga1", subAccountID: "sa2", runtimeID: "runtimeID3"}), + *fixInstance(instanceData{val: "A4", globalAccountID: "ga2", subAccountID: "sa3", runtimeID: "runtimeID4"}), + *fixInstance(instanceData{val: "A5", globalAccountID: "ga2", subAccountID: "sa4", runtimeID: "runtimeID5"}), + *fixInstance(instanceData{val: "A6", globalAccountID: "ga2", subAccountID: "sa5", runtimeID: ""}), + *fixInstance(instanceData{val: "A7", globalAccountID: "ga2", subAccountID: "sa6", runtimeID: "runtimeID7"}), + } + + for _, i := range fixInstances { + err = brokerStorage.Instances().Insert(i) + require.NoError(t, err) + } + + // when + subaccounts, err := brokerStorage.Instances().GetDistinctSubAccounts() + require.NoError(t, err) + + // then + assert.Equal(t, 6, len(subaccounts)) + }) + t.Run("Should fetch instances along with their operations", func(t *testing.T) { storageCleanup, brokerStorage, err := GetStorageForDatabaseTests() require.NoError(t, err) @@ -813,6 +846,7 @@ type instanceData struct { val string globalAccountID string subAccountID string + runtimeID string expired bool trial bool deletedAt time.Time diff --git a/internal/storage/driver/postsql/subaccount_state.go b/internal/storage/driver/postsql/subaccount_state.go new file mode 100644 index 0000000000..09866c931a --- /dev/null +++ b/internal/storage/driver/postsql/subaccount_state.go @@ -0,0 +1,100 @@ +package postsql + +import ( + "fmt" + + "github.com/kyma-project/kyma-environment-broker/internal" + "github.com/kyma-project/kyma-environment-broker/internal/storage/dberr" + "github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel" + "github.com/kyma-project/kyma-environment-broker/internal/storage/postsql" + log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/wait" +) + +type SubaccountState struct { + postsql.Factory +} + +func NewSubaccountStates(sess postsql.Factory) *SubaccountState { + return &SubaccountState{ + Factory: sess, + } +} + +func (s *SubaccountState) UpsertState(subaccountState internal.SubaccountState) error { + state, err := s.subaccountStateToDB(subaccountState) + if err != nil { + return err + } + sess := s.NewWriteSession() + return wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) { + err := sess.UpsertSubaccountState(state) + if err != nil { + log.Errorf("while upserting subaccount state ID %s: %v", subaccountState.ID, err) + return false, nil + } + return true, nil + }) +} + +func (s *SubaccountState) DeleteState(subaccountID string) error { + sess := s.NewWriteSession() + return sess.DeleteState(subaccountID) +} + +func (s *SubaccountState) ListStates() ([]internal.SubaccountState, error) { + sess := s.NewReadSession() + states := make([]dbmodel.SubaccountStateDTO, 0) + var lastErr dberr.Error + err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) { + states, lastErr = sess.ListSubaccountStates() + if lastErr != nil { + if dberr.IsNotFound(lastErr) { + return false, dberr.NotFound("subaccount_states not found") + } + log.Errorf("while getting subaccount states: %v", lastErr) + return false, nil + } + return true, nil + }) + if err != nil { + return nil, lastErr + } + result, err := s.toSubaccountStates(states) + if err != nil { + return nil, err + } + return result, nil +} + +func (s *SubaccountState) subaccountStateToDB(state internal.SubaccountState) (dbmodel.SubaccountStateDTO, error) { + return dbmodel.SubaccountStateDTO{ + ID: state.ID, + BetaEnabled: state.BetaEnabled, + UsedForProduction: state.UsedForProduction, + ModifiedAt: state.ModifiedAt, + }, nil +} + +func (s *SubaccountState) toSubaccountState(dto *dbmodel.SubaccountStateDTO) (internal.SubaccountState, error) { + return internal.SubaccountState{ + ID: dto.ID, + BetaEnabled: dto.BetaEnabled, + UsedForProduction: dto.UsedForProduction, + ModifiedAt: dto.ModifiedAt, + }, nil +} + +func (s *SubaccountState) toSubaccountStates(states []dbmodel.SubaccountStateDTO) ([]internal.SubaccountState, error) { + result := make([]internal.SubaccountState, 0) + + for _, state := range states { + r, err := s.toSubaccountState(&state) + if err != nil { + return nil, fmt.Errorf("while converting subaccount states: %w", err) + } + result = append(result, r) + } + + return result, nil +} diff --git a/internal/storage/driver/postsql/subaccount_state_test.go b/internal/storage/driver/postsql/subaccount_state_test.go new file mode 100644 index 0000000000..0b32a1af68 --- /dev/null +++ b/internal/storage/driver/postsql/subaccount_state_test.go @@ -0,0 +1,100 @@ +package postsql_test + +import ( + "github.com/kyma-project/kyma-environment-broker/internal" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + subaccountID1 = "subaccountID1" + subaccountID2 = "subaccountID2" +) + +var ( + givenSubaccount1State1 = internal.SubaccountState{ + ID: subaccountID1, + BetaEnabled: "true", + UsedForProduction: "NOT_SET", + ModifiedAt: 10, + } + + givenSubaccount1State2 = internal.SubaccountState{ + ID: subaccountID1, + BetaEnabled: "false", + UsedForProduction: "NOT_SET", + ModifiedAt: 54, + } + givenSubaccount2State3 = internal.SubaccountState{ + ID: subaccountID2, + BetaEnabled: "true", + UsedForProduction: "USED_FOR_PRODUCTION", + ModifiedAt: 108, + } +) + +func TestSubaccountState(t *testing.T) { + storageCleanup, brokerStorage, err := GetStorageForDatabaseTests() + require.NoError(t, err) + require.NotNil(t, brokerStorage) + defer func() { + err := storageCleanup() + assert.NoError(t, err) + }() + + svc := brokerStorage.SubaccountStates() + + t.Run("should insert and fetch state", func(t *testing.T) { + + err = svc.UpsertState(givenSubaccount1State1) + require.NoError(t, err) + + subaccountStates, err := svc.ListStates() + require.NoError(t, err) + assert.Len(t, subaccountStates, 1) + assert.Equal(t, "true", subaccountStates[0].BetaEnabled) + assert.Equal(t, "NOT_SET", subaccountStates[0].UsedForProduction) + }) + + t.Run("should update subaccount state and then fetch it", func(t *testing.T) { + + err = svc.UpsertState(givenSubaccount1State2) + + require.NoError(t, err) + subaccountStates, err := svc.ListStates() + require.NoError(t, err) + assert.Len(t, subaccountStates, 1) + assert.Equal(t, "false", subaccountStates[0].BetaEnabled) + assert.Equal(t, "NOT_SET", subaccountStates[0].UsedForProduction) + }) + + t.Run("insert second subaccount state and fetch two states", func(t *testing.T) { + + err = svc.UpsertState(givenSubaccount2State3) + + require.NoError(t, err) + subaccountStates, err := svc.ListStates() + require.NoError(t, err) + assert.Len(t, subaccountStates, 2) + expectedStates := []internal.SubaccountState{givenSubaccount1State2, givenSubaccount2State3} + + for _, expectedState := range expectedStates { + assert.Contains(t, subaccountStates, expectedState) + + } + }) + + t.Run("delete second subaccount state and fetch one state", func(t *testing.T) { + + err = svc.DeleteState(subaccountID2) + + require.NoError(t, err) + subaccountStates, err := svc.ListStates() + require.NoError(t, err) + assert.Equal(t, givenSubaccount1State2, subaccountStates[0]) + assert.Len(t, subaccountStates, 1) + }) + +} diff --git a/internal/storage/ext.go b/internal/storage/ext.go index 2ecc2ca97d..b120109110 100644 --- a/internal/storage/ext.go +++ b/internal/storage/ext.go @@ -19,6 +19,7 @@ type Instances interface { Delete(instanceID string) error GetInstanceStats() (internal.InstanceStats, error) GetERSContextStats() (internal.ERSContextStats, error) + GetDistinctSubAccounts() ([]string, error) GetNumberOfInstancesForGlobalAccountID(globalAccountID string) (int, error) List(dbmodel.InstanceFilter) ([]internal.Instance, int, int, error) @@ -123,3 +124,9 @@ type Events interface { InsertEvent(level events.EventLevel, message, instanceID, operationID string) ListEvents(filter events.EventFilter) ([]events.EventDTO, error) } + +type SubaccountStates interface { + UpsertState(state internal.SubaccountState) error + DeleteState(subaccountID string) error + ListStates() ([]internal.SubaccountState, error) +} diff --git a/internal/storage/postsql/factory.go b/internal/storage/postsql/factory.go index e76fb25df1..e708a5fae3 100644 --- a/internal/storage/postsql/factory.go +++ b/internal/storage/postsql/factory.go @@ -52,6 +52,8 @@ type ReadSession interface { GetLatestRuntimeStateWithKymaVersionByRuntimeID(runtimeID string) (dbmodel.RuntimeStateDTO, dberr.Error) GetLatestRuntimeStateWithOIDCConfigByRuntimeID(runtimeID string) (dbmodel.RuntimeStateDTO, dberr.Error) ListEvents(filter events.EventFilter) ([]events.EventDTO, error) + GetDistinctSubAccounts() ([]string, dberr.Error) + ListSubaccountStates() ([]dbmodel.SubaccountStateDTO, dberr.Error) GetInstanceArchivedByID(id string) (dbmodel.InstanceArchivedDTO, error) } @@ -67,6 +69,8 @@ type WriteSession interface { InsertRuntimeState(state dbmodel.RuntimeStateDTO) dberr.Error InsertEvent(level events.EventLevel, message, instanceID, operationID string) dberr.Error DeleteEvents(until time.Time) dberr.Error + UpsertSubaccountState(state dbmodel.SubaccountStateDTO) dberr.Error + DeleteState(id string) dberr.Error DeleteRuntimeStatesByOperationID(operationID string) error DeleteOperationByID(operationID string) dberr.Error InsertInstanceArchived(instance dbmodel.InstanceArchivedDTO) dberr.Error diff --git a/internal/storage/postsql/init.go b/internal/storage/postsql/init.go index bd3942e256..83892182c8 100644 --- a/internal/storage/postsql/init.go +++ b/internal/storage/postsql/init.go @@ -17,8 +17,9 @@ const ( OperationTableName = "operations" OrchestrationTableName = "orchestrations" RuntimeStateTableName = "runtime_states" - InstancesArchivedTableName = "instances_archived" + SubaccountStatesTableName = "subaccount_states" CreatedAtField = "created_at" + InstancesArchivedTableName = "instances_archived" ) // InitializeDatabase opens database connection and initializes schema if it does not exist diff --git a/internal/storage/postsql/read.go b/internal/storage/postsql/read.go index 41f56b54a4..d26276b9db 100644 --- a/internal/storage/postsql/read.go +++ b/internal/storage/postsql/read.go @@ -21,6 +21,35 @@ type readSession struct { session *dbr.Session } +func (r readSession) ListSubaccountStates() ([]dbmodel.SubaccountStateDTO, dberr.Error) { + var states []dbmodel.SubaccountStateDTO + + _, err := r.session. + Select("*"). + From(SubaccountStatesTableName). + Load(&states) + if err != nil { + return nil, dberr.Internal("Failed to get subaccount states: %s", err) + } + return states, nil +} + +func (r readSession) GetDistinctSubAccounts() ([]string, dberr.Error) { + var subAccounts []string + + err := r.session. + Select("distinct(sub_account_id)"). + From(InstancesTableName). + Where(dbr.Neq("runtime_id", "")). + LoadOne(&subAccounts) + + if err != nil { + return []string{}, dberr.Internal("Failed to get distinct subaccounts: %s", err) + } + + return subAccounts, nil +} + func (r readSession) getInstancesJoinedWithOperationStatement() *dbr.SelectStmt { join := fmt.Sprintf("%s.instance_id = %s.instance_id", InstancesTableName, OperationTableName) stmt := r.session. diff --git a/internal/storage/postsql/write.go b/internal/storage/postsql/write.go index 4ceb387505..11c7173b5c 100644 --- a/internal/storage/postsql/write.go +++ b/internal/storage/postsql/write.go @@ -247,6 +247,41 @@ func (ws writeSession) DeleteRuntimeStatesByOperationID(operationID string) erro return nil } +func (ws writeSession) UpsertSubaccountState(state dbmodel.SubaccountStateDTO) dberr.Error { + result, err := ws.update(SubaccountStatesTableName). + Where(dbr.Eq("id", state.ID)). + Set("beta_enabled", state.BetaEnabled). + Set("used_for_production", state.UsedForProduction). + Set("modified_at", state.ModifiedAt). + Exec() + if err != nil { + return dberr.Internal("Failed to update record to subaccount_states table: %s", err) + } + rAffected, err := result.RowsAffected() + if rAffected == int64(0) { + _, err = ws.insertInto(SubaccountStatesTableName). + Pair("id", state.ID). + Pair("beta_enabled", state.BetaEnabled). + Pair("used_for_production", state.UsedForProduction). + Pair("modified_at", state.ModifiedAt). + Exec() + if err != nil { + return dberr.Internal("Failed to upsert record to subaccount_states table: %s", err) + } + } + return nil +} + +func (ws writeSession) DeleteState(subaccountID string) dberr.Error { + _, err := ws.deleteFrom(SubaccountStatesTableName). + Where(dbr.Eq("id", subaccountID)). + Exec() + if err != nil { + return dberr.Internal("failed to delete state for subaccount %s: %v", subaccountID, err) + } + return nil +} + func (ws writeSession) UpdateOperation(op dbmodel.OperationDTO) dberr.Error { res, err := ws.update(OperationTableName). Where(dbr.Eq("id", op.ID)). diff --git a/internal/storage/storage.go b/internal/storage/storage.go index adad71f690..9a9554c6fd 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -22,6 +22,7 @@ type BrokerStorage interface { Deprovisioning() Deprovisioning Orchestrations() Orchestrations RuntimeStates() RuntimeStates + SubaccountStates() SubaccountStates Events() Events InstancesArchived() InstancesArchived } @@ -52,6 +53,7 @@ func NewFromConfig(cfg Config, evcfg events.Config, cipher postgres.Cipher, log orchestrations: postgres.NewOrchestrations(fact), runtimeStates: postgres.NewRuntimeStates(fact, cipher), events: events.New(evcfg, eventstorage.New(fact, log)), + subaccountStates: postgres.NewSubaccountStates(fact), instancesArchived: postgres.NewInstanceArchived(fact), }, connection, nil } @@ -64,6 +66,7 @@ func NewMemoryStorage() BrokerStorage { orchestrations: memory.NewOrchestrations(), runtimeStates: memory.NewRuntimeStates(), events: events.New(events.Config{}, NewInMemoryEvents()), + subaccountStates: memory.NewSubaccountStates(), instancesArchived: memory.NewInstanceArchivedInMemoryStorage(), } } @@ -122,6 +125,7 @@ type storage struct { orchestrations Orchestrations runtimeStates RuntimeStates events Events + subaccountStates SubaccountStates instancesArchived InstancesArchived } @@ -153,6 +157,10 @@ func (s storage) Events() Events { return s.events } +func (s storage) SubaccountStates() SubaccountStates { + return s.subaccountStates +} + func (s storage) InstancesArchived() InstancesArchived { return s.instancesArchived } From 717489ebd30998adb1b27cafad2ce5afcc5a2f71 Mon Sep 17 00:00:00 2001 From: kyma-btp-manager-serviceuser Date: Wed, 6 Mar 2024 15:23:05 +0100 Subject: [PATCH 2/3] goimports --- internal/storage/driver/memory/subaccount_state_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/storage/driver/memory/subaccount_state_test.go b/internal/storage/driver/memory/subaccount_state_test.go index 6bb9b9df3a..0e775d4188 100644 --- a/internal/storage/driver/memory/subaccount_state_test.go +++ b/internal/storage/driver/memory/subaccount_state_test.go @@ -1,9 +1,10 @@ package memory import ( - "github.com/kyma-project/kyma-environment-broker/internal" "testing" + "github.com/kyma-project/kyma-environment-broker/internal" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) From 5aa5fc7a6533b485ee523214f9a999968a7dd0eb Mon Sep 17 00:00:00 2001 From: kyma-btp-manager-serviceuser Date: Wed, 6 Mar 2024 15:38:58 +0100 Subject: [PATCH 3/3] goimports again --- internal/storage/driver/postsql/subaccount_state_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/storage/driver/postsql/subaccount_state_test.go b/internal/storage/driver/postsql/subaccount_state_test.go index 0b32a1af68..446fa39f47 100644 --- a/internal/storage/driver/postsql/subaccount_state_test.go +++ b/internal/storage/driver/postsql/subaccount_state_test.go @@ -1,9 +1,10 @@ package postsql_test import ( - "github.com/kyma-project/kyma-environment-broker/internal" "testing" + "github.com/kyma-project/kyma-environment-broker/internal" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" )