From 1c46a393bf296fd57d95e6c01019b259cba881e1 Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Mon, 29 Jun 2026 12:02:46 +0200 Subject: [PATCH 1/7] [raft/store] Update Transact signature --- cmds/db-manager/cleanup/evict.go | 5 +- pkg/memstore/store.go | 5 +- pkg/raftstore/store.go | 5 +- pkg/rid/application/application_test.go | 5 +- pkg/rid/application/isa.go | 13 ++--- pkg/rid/application/subscription.go | 13 ++--- pkg/rid/store/sqlstore/store_test.go | 17 ++++--- pkg/scd/constraints_handler.go | 9 ++-- pkg/scd/operational_intents_handler.go | 9 ++-- pkg/scd/subscriptions_handler.go | 9 ++-- pkg/scd/uss_availability_handler.go | 5 +- pkg/sqlstore/store.go | 24 +++++---- pkg/store/store.go | 67 +++++++++++++++++++++++-- 13 files changed, 132 insertions(+), 54 deletions(-) diff --git a/cmds/db-manager/cleanup/evict.go b/cmds/db-manager/cleanup/evict.go index 572b90f5b..d5594bca4 100644 --- a/cmds/db-manager/cleanup/evict.go +++ b/cmds/db-manager/cleanup/evict.go @@ -14,6 +14,7 @@ import ( scdmodels "github.com/interuss/dss/pkg/scd/models" scdrepos "github.com/interuss/dss/pkg/scd/repos" scds "github.com/interuss/dss/pkg/scd/store" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -100,7 +101,7 @@ func evict(cmd *cobra.Command, _ []string) error { } return nil } - if err = scdStore.Transact(ctx, scdAction); err != nil { + if _, err = scdStore.Transact(ctx, store.NewActionFunction(scdAction)); err != nil { return fmt.Errorf("failed to execute SCD transaction: %w", err) } @@ -145,7 +146,7 @@ func evict(cmd *cobra.Command, _ []string) error { return nil } - if err = ridStore.Transact(ctx, ridAction); err != nil { + if _, err = ridStore.Transact(ctx, store.NewActionFunction(ridAction)); err != nil { return fmt.Errorf("failed to execute RID transaction: %w", err) } diff --git a/pkg/memstore/store.go b/pkg/memstore/store.go index 9c850f2c3..f9d183120 100644 --- a/pkg/memstore/store.go +++ b/pkg/memstore/store.go @@ -12,6 +12,7 @@ import ( dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/logging" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -50,8 +51,8 @@ func Init[R any](ctx context.Context, logger *zap.Logger, name string, r MemRepo return store, nil } -func (s *Store[R]) Transact(ctx context.Context, _ func(context.Context, R) error) error { - return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "Transact not implemented for memstore") +func (s *Store[R]) Transact(ctx context.Context, _ store.Action[R]) (any, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "Transact not implemented for memstore") } func (s *Store[R]) Interact(_ context.Context) (R, error) { diff --git a/pkg/raftstore/store.go b/pkg/raftstore/store.go index ee2b40b51..392ae33a8 100644 --- a/pkg/raftstore/store.go +++ b/pkg/raftstore/store.go @@ -5,6 +5,7 @@ import ( "github.com/interuss/dss/pkg/raftstore/consensus" raftparams "github.com/interuss/dss/pkg/raftstore/params" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -29,9 +30,9 @@ func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.Conn } // Transact proposes the entry to Raft and blocks until it is committed and applied. -func (s *Store[R]) Transact(ctx context.Context, f func(context.Context, R) error) error { +func (s *Store[R]) Transact(ctx context.Context, action store.Action[R]) (any, error) { // TODO: implement - return nil + return nil, nil } // Interact returns a repository that can be used to query the store without proposing a Raft entry. diff --git a/pkg/rid/application/application_test.go b/pkg/rid/application/application_test.go index 0cc3fd885..3e6e84b1f 100644 --- a/pkg/rid/application/application_test.go +++ b/pkg/rid/application/application_test.go @@ -13,6 +13,7 @@ import ( dssql "github.com/interuss/dss/pkg/sql" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/sqlstore/params" + dssstore "github.com/interuss/dss/pkg/store" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" @@ -35,8 +36,8 @@ func (s *mockRepo) Interact(ctx context.Context) (repos.Repository, error) { return s, nil } -func (s *mockRepo) Transact(ctx context.Context, f func(ctx context.Context, repo repos.Repository) error) error { - return f(ctx, s) +func (s *mockRepo) Transact(ctx context.Context, action dssstore.Action[repos.Repository]) (any, error) { + return action.Execute(ctx, s) } func (s *mockRepo) Close() error { diff --git a/pkg/rid/application/isa.go b/pkg/rid/application/isa.go index ea402af60..b4cf0aaff 100644 --- a/pkg/rid/application/isa.go +++ b/pkg/rid/application/isa.go @@ -10,6 +10,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" ) @@ -63,7 +64,7 @@ func (a *app) DeleteISA(ctx context.Context, id dssmodels.ID, owner dssmodels.Ow subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { old, err := repo.GetISA(ctx, id, true) switch { case err != nil: @@ -88,7 +89,7 @@ func (a *app) DeleteISA(ctx context.Context, id dssmodels.ID, owner dssmodels.Ow return stacktrace.Propagate(err, "Error updating notification indices") } return nil - }) + })) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } @@ -104,7 +105,7 @@ func (a *app) InsertISA(ctx context.Context, isa *ridmodels.IdentificationServic subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { // ensure it doesn't exist yet old, err := repo.GetISA(ctx, isa.ID, false) if err != nil { @@ -126,7 +127,7 @@ func (a *app) InsertISA(ctx context.Context, isa *ridmodels.IdentificationServic return stacktrace.Propagate(err, "Error inserting ISA") } return nil - }) + })) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } @@ -138,7 +139,7 @@ func (a *app) UpdateISA(ctx context.Context, isa *ridmodels.IdentificationServic subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { var err error old, err := repo.GetISA(ctx, isa.ID, true) @@ -176,7 +177,7 @@ func (a *app) UpdateISA(ctx context.Context, isa *ridmodels.IdentificationServic return stacktrace.Propagate(err, "Error updating notification indices") } return nil - }) + })) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } diff --git a/pkg/rid/application/subscription.go b/pkg/rid/application/subscription.go index 33ba2af36..b4bb91b75 100644 --- a/pkg/rid/application/subscription.go +++ b/pkg/rid/application/subscription.go @@ -8,6 +8,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -60,7 +61,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) return nil, stacktrace.Propagate(err, "Unable to adjust time range") } var sub *ridmodels.Subscription - err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { // ensure it doesn't exist yet old, err := repo.GetSubscription(ctx, s.ID) @@ -90,7 +91,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) } return nil - }) + })) return sub, err } @@ -98,7 +99,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) func (a *app) UpdateSubscription(ctx context.Context, s *ridmodels.Subscription) (*ridmodels.Subscription, error) { var sub *ridmodels.Subscription - err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { old, err := repo.GetSubscription(ctx, s.ID) switch { case err != nil: @@ -138,14 +139,14 @@ func (a *app) UpdateSubscription(ctx context.Context, s *ridmodels.Subscription) return stacktrace.Propagate(err, "Error updating Subscription in repo") } return nil - }) + })) return sub, err } // DeleteSubscription deletes the Subscription identified by "id" and owned by "owner". func (a *app) DeleteSubscription(ctx context.Context, id dssmodels.ID, owner dssmodels.Owner, version *dssmodels.Version) (*ridmodels.Subscription, error) { var ret *ridmodels.Subscription - err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { var err error old, err := repo.GetSubscription(ctx, id) switch { @@ -168,6 +169,6 @@ func (a *app) DeleteSubscription(ctx context.Context, id dssmodels.ID, owner dss return stacktrace.Propagate(err, "Error deleting Subscription from repo") } return nil - }) + })) return ret, err } diff --git a/pkg/rid/store/sqlstore/store_test.go b/pkg/rid/store/sqlstore/store_test.go index 275a1a332..e6de252c5 100644 --- a/pkg/rid/store/sqlstore/store_test.go +++ b/pkg/rid/store/sqlstore/store_test.go @@ -13,6 +13,7 @@ import ( "github.com/interuss/dss/pkg/rid/repos" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/sqlstore/params" + dssstore "github.com/interuss/dss/pkg/store" "github.com/jackc/pgx/v5/pgconn" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" @@ -97,12 +98,12 @@ func TestTxnRetrier(t *testing.T) { require.NotNil(t, store) defer tearDownStore() - err := store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { // can query within this isa, err := repo.InsertISA(ctx, serviceArea) require.NotNil(t, isa) return err - }) + })) require.NoError(t, err) // can query afterwads repo, err := store.Interact(ctx) @@ -118,12 +119,12 @@ func TestTxnRetrier(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 20*time.Millisecond) defer cancel() count := 0 - err = store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err = store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { // can query within this count++ // Postgre retryable error return &pgconn.PgError{Code: "40001"} - }) + })) require.Error(t, err) // Ensure it was retried. require.Greater(t, count, 1) @@ -141,13 +142,13 @@ func TestTransactor(t *testing.T) { subscription2 := subscriptionsPool[1].input txnCount := 0 - err := store.Transact(ctx, func(ctx context.Context, s1 repos.Repository) error { + _, err := store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, s1 repos.Repository) error { // We should get to this retry, then return nothing. if txnCount > 0 { return errors.New("already failed") } txnCount++ - err := store.Transact(ctx, func(ctx context.Context, s2 repos.Repository) error { + _, err := store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, s2 repos.Repository) error { subs, err := s1.SearchSubscriptions(ctx, subscription1.Cells) require.NoError(t, err) require.Len(t, subs, 0) @@ -164,9 +165,9 @@ func TestTransactor(t *testing.T) { require.NoError(t, err) return nil - }) + })) return err - }) + })) require.Error(t, err) repo, err := store.Interact(ctx) diff --git a/pkg/scd/constraints_handler.go b/pkg/scd/constraints_handler.go index 0688176c7..de53cdd2d 100644 --- a/pkg/scd/constraints_handler.go +++ b/pkg/scd/constraints_handler.go @@ -11,6 +11,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jackc/pgx/v5" ) @@ -88,7 +89,7 @@ func (a *Server) DeleteConstraintReference(ctx context.Context, req *restapi.Del return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not delete constraint") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -147,7 +148,7 @@ func (a *Server) GetConstraintReference(ctx context.Context, req *restapi.GetCon return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not get constraint") if stacktrace.GetCode(err) == dsserr.NotFound { @@ -306,7 +307,7 @@ func (a *Server) PutConstraintReference(ctx context.Context, manager string, ent return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { return nil, err // No need to Propagate this error as this is not a useful stacktrace line } @@ -434,7 +435,7 @@ func (a *Server) QueryConstraintReferences(ctx context.Context, req *restapi.Que return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { return restapi.QueryConstraintReferencesResponseSet{Response500: &api.InternalServerErrorBody{ ErrorMessage: *dsserr.Handle(ctx, stacktrace.Propagate(err, "Got an unexpected error"))}} diff --git a/pkg/scd/operational_intents_handler.go b/pkg/scd/operational_intents_handler.go index f611797d0..6ad1a9f86 100644 --- a/pkg/scd/operational_intents_handler.go +++ b/pkg/scd/operational_intents_handler.go @@ -13,6 +13,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" ) @@ -165,7 +166,7 @@ func (a *Server) DeleteOperationalIntentReference(ctx context.Context, req *rest return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not delete operational intent") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -221,7 +222,7 @@ func (a *Server) GetOperationalIntentReference(ctx context.Context, req *restapi return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not get operational intent") if stacktrace.GetCode(err) == dsserr.NotFound { @@ -288,7 +289,7 @@ func (a *Server) QueryOperationalIntentReferences(ctx context.Context, req *rest return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not query operational intent") if stacktrace.GetCode(err) == dsserr.BadRequest { @@ -934,7 +935,7 @@ func (a *Server) upsertOperationalIntentReference(ctx context.Context, now time. return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { return nil, responseConflict, err // No need to Propagate this error as this is not a useful stacktrace line } diff --git a/pkg/scd/subscriptions_handler.go b/pkg/scd/subscriptions_handler.go index 98ab9fed2..c01a5e266 100644 --- a/pkg/scd/subscriptions_handler.go +++ b/pkg/scd/subscriptions_handler.go @@ -12,6 +12,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jonboulle/clockwork" ) @@ -276,7 +277,7 @@ func (a *Server) PutSubscription(ctx context.Context, manager string, subscripti return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { return nil, err // No need to Propagate this error as this is not a useful stacktrace line } @@ -340,7 +341,7 @@ func (a *Server) GetSubscription(ctx context.Context, req *restapi.GetSubscripti return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not get subscription") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -424,7 +425,7 @@ func (a *Server) QuerySubscriptions(ctx context.Context, req *restapi.QuerySubsc return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -517,7 +518,7 @@ func (a *Server) DeleteSubscription(ctx context.Context, req *restapi.DeleteSubs return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { err = stacktrace.Propagate(err, "Could not delete subscription") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} diff --git a/pkg/scd/uss_availability_handler.go b/pkg/scd/uss_availability_handler.go index 517a9859a..3d5857f5f 100644 --- a/pkg/scd/uss_availability_handler.go +++ b/pkg/scd/uss_availability_handler.go @@ -10,6 +10,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jackc/pgx/v5" ) @@ -51,7 +52,7 @@ func (a *Server) GetUssAvailability(ctx context.Context, req *restapi.GetUssAvai return nil } - err := a.Store.Transact(ctx, action) + _, err := a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { // In case of older DB versions where availability table doesn't exist if strings.Contains(err.Error(), "does not exist") { @@ -121,7 +122,7 @@ func (a *Server) SetUssAvailability(ctx context.Context, req *restapi.SetUssAvai } return nil } - err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) if err != nil { // In case of older DB versions where availability table doesn't exist if strings.Contains(err.Error(), "does not exist") { diff --git a/pkg/sqlstore/store.go b/pkg/sqlstore/store.go index 92437c190..d5a213916 100644 --- a/pkg/sqlstore/store.go +++ b/pkg/sqlstore/store.go @@ -176,25 +176,31 @@ func (s *Store[R]) Interact(_ context.Context) (R, error) { return s.newRepo(s.Pool), nil } -func (s *Store[R]) Transact(ctx context.Context, f func(context.Context, R) error) error { +func (s *Store[R]) Transact(ctx context.Context, action store.Action[R]) (any, error) { if s.Version.Type == Yugabyte { - return s.transactYugabyte(ctx, f) + return s.transactYugabyte(ctx, action) } ctx = crdb.WithMaxRetries(ctx, s.maxRetries) - return crdbpgx.ExecuteTx(ctx, s.Pool, pgx.TxOptions{IsoLevel: pgx.Serializable}, func(tx pgx.Tx) error { - return f(ctx, s.newRepo(tx)) + var result any + var err error + err = crdbpgx.ExecuteTx(ctx, s.Pool, pgx.TxOptions{IsoLevel: pgx.Serializable}, func(tx pgx.Tx) error { + result, err = action.Execute(ctx, s.newRepo(tx)) + return err }) + return result, err } -func (s *Store[R]) transactYugabyte(ctx context.Context, f func(context.Context, R) error) error { +func (s *Store[R]) transactYugabyte(ctx context.Context, action store.Action[R]) (any, error) { + var result any var err error for attempt := 0; attempt <= s.maxRetries; attempt++ { err = pgx.BeginTxFunc(ctx, s.Pool, pgx.TxOptions{IsoLevel: pgx.Serializable}, func(tx pgx.Tx) error { - return f(ctx, s.newRepo(tx)) + result, err = action.Execute(ctx, s.newRepo(tx)) + return err }) if err == nil || !isYugabyteRetryable(err) { - return err + return result, err } if attempt == s.maxRetries { break @@ -202,11 +208,11 @@ func (s *Store[R]) transactYugabyte(ctx context.Context, f func(context.Context, backoff := time.Duration(1< Date: Mon, 6 Jul 2026 10:54:44 +0200 Subject: [PATCH 2/7] pass operation to transact --- cmds/db-manager/cleanup/evict.go | 5 +- pkg/aux_/actions/dispatch.go | 13 ++++++ pkg/aux_/store/sqlstore/store.go | 2 + pkg/memstore/store.go | 3 +- pkg/raftstore/store.go | 3 +- pkg/rid/actions/dispatch.go | 13 ++++++ pkg/rid/application/application_test.go | 10 ++-- pkg/rid/application/isa.go | 13 +++--- pkg/rid/application/subscription.go | 13 +++--- pkg/rid/store/sqlstore/store.go | 2 + pkg/rid/store/sqlstore/store_test.go | 17 ++++--- pkg/scd/actions/dispatch.go | 13 ++++++ pkg/scd/constraints_handler.go | 9 ++-- pkg/scd/operational_intents_handler.go | 9 ++-- pkg/scd/store/sqlstore/store.go | 2 + pkg/scd/subscriptions_handler.go | 9 ++-- pkg/scd/uss_availability_handler.go | 5 +- pkg/sqlstore/store.go | 26 +++++++++-- pkg/store/store.go | 62 +++---------------------- 19 files changed, 118 insertions(+), 111 deletions(-) create mode 100644 pkg/aux_/actions/dispatch.go create mode 100644 pkg/rid/actions/dispatch.go create mode 100644 pkg/scd/actions/dispatch.go diff --git a/cmds/db-manager/cleanup/evict.go b/cmds/db-manager/cleanup/evict.go index d5594bca4..e50f047fa 100644 --- a/cmds/db-manager/cleanup/evict.go +++ b/cmds/db-manager/cleanup/evict.go @@ -14,7 +14,6 @@ import ( scdmodels "github.com/interuss/dss/pkg/scd/models" scdrepos "github.com/interuss/dss/pkg/scd/repos" scds "github.com/interuss/dss/pkg/scd/store" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -101,7 +100,7 @@ func evict(cmd *cobra.Command, _ []string) error { } return nil } - if _, err = scdStore.Transact(ctx, store.NewActionFunction(scdAction)); err != nil { + if _, err = scdStore.Transact(ctx, scdAction); err != nil { return fmt.Errorf("failed to execute SCD transaction: %w", err) } @@ -146,7 +145,7 @@ func evict(cmd *cobra.Command, _ []string) error { return nil } - if _, err = ridStore.Transact(ctx, store.NewActionFunction(ridAction)); err != nil { + if _, err = ridStore.Transact(ctx, ridAction); err != nil { return fmt.Errorf("failed to execute RID transaction: %w", err) } diff --git a/pkg/aux_/actions/dispatch.go b/pkg/aux_/actions/dispatch.go new file mode 100644 index 000000000..c862ee96f --- /dev/null +++ b/pkg/aux_/actions/dispatch.go @@ -0,0 +1,13 @@ +package actions + +import ( + "context" + + "github.com/interuss/dss/pkg/aux_/repos" + "github.com/interuss/stacktrace" +) + +// Dispatch routes a restapi operation to the appropriate execution function. +func Dispatch(_ context.Context, _ repos.Repository, op any) (any, error) { + return nil, stacktrace.NewError("dispatch not implemented for op %T", op) +} diff --git a/pkg/aux_/store/sqlstore/store.go b/pkg/aux_/store/sqlstore/store.go index 11c5f5e7a..c7dace41f 100644 --- a/pkg/aux_/store/sqlstore/store.go +++ b/pkg/aux_/store/sqlstore/store.go @@ -3,6 +3,7 @@ package sqlstore import ( "context" + "github.com/interuss/dss/pkg/aux_/actions" "github.com/interuss/dss/pkg/aux_/repos" "github.com/interuss/dss/pkg/logging" dssql "github.com/interuss/dss/pkg/sql" @@ -39,5 +40,6 @@ func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (*sqlstor version: version, } }, + Dispatch: actions.Dispatch, }, withCheckCron) } diff --git a/pkg/memstore/store.go b/pkg/memstore/store.go index f9d183120..ee1bde05e 100644 --- a/pkg/memstore/store.go +++ b/pkg/memstore/store.go @@ -12,7 +12,6 @@ import ( dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/logging" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -51,7 +50,7 @@ func Init[R any](ctx context.Context, logger *zap.Logger, name string, r MemRepo return store, nil } -func (s *Store[R]) Transact(ctx context.Context, _ store.Action[R]) (any, error) { +func (s *Store[R]) Transact(ctx context.Context, _ any) (any, error) { return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "Transact not implemented for memstore") } diff --git a/pkg/raftstore/store.go b/pkg/raftstore/store.go index 392ae33a8..8fd3c6a54 100644 --- a/pkg/raftstore/store.go +++ b/pkg/raftstore/store.go @@ -5,7 +5,6 @@ import ( "github.com/interuss/dss/pkg/raftstore/consensus" raftparams "github.com/interuss/dss/pkg/raftstore/params" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -30,7 +29,7 @@ func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.Conn } // Transact proposes the entry to Raft and blocks until it is committed and applied. -func (s *Store[R]) Transact(ctx context.Context, action store.Action[R]) (any, error) { +func (s *Store[R]) Transact(_ context.Context, _ any) (any, error) { // TODO: implement return nil, nil } diff --git a/pkg/rid/actions/dispatch.go b/pkg/rid/actions/dispatch.go new file mode 100644 index 000000000..15c3e71cc --- /dev/null +++ b/pkg/rid/actions/dispatch.go @@ -0,0 +1,13 @@ +package actions + +import ( + "context" + + "github.com/interuss/dss/pkg/rid/repos" + "github.com/interuss/stacktrace" +) + +// Dispatch routes a restapi operation to the appropriate execution function. +func Dispatch(_ context.Context, _ repos.Repository, op any) (any, error) { + return nil, stacktrace.NewError("dispatch not implemented for op %T", op) +} diff --git a/pkg/rid/application/application_test.go b/pkg/rid/application/application_test.go index 3e6e84b1f..93c754f99 100644 --- a/pkg/rid/application/application_test.go +++ b/pkg/rid/application/application_test.go @@ -13,7 +13,7 @@ import ( dssql "github.com/interuss/dss/pkg/sql" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/sqlstore/params" - dssstore "github.com/interuss/dss/pkg/store" + "github.com/interuss/stacktrace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" @@ -36,8 +36,12 @@ func (s *mockRepo) Interact(ctx context.Context) (repos.Repository, error) { return s, nil } -func (s *mockRepo) Transact(ctx context.Context, action dssstore.Action[repos.Repository]) (any, error) { - return action.Execute(ctx, s) +func (s *mockRepo) Transact(ctx context.Context, op any) (any, error) { + // TODO: update this when Dispatch is implemented for rid operations. + if f, ok := op.(func(context.Context, repos.Repository) error); ok { + return nil, f(ctx, s) + } + return nil, stacktrace.NewError("unsupported operation type %T", op) } func (s *mockRepo) Close() error { diff --git a/pkg/rid/application/isa.go b/pkg/rid/application/isa.go index b4cf0aaff..11bba7654 100644 --- a/pkg/rid/application/isa.go +++ b/pkg/rid/application/isa.go @@ -10,7 +10,6 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" ) @@ -64,7 +63,7 @@ func (a *app) DeleteISA(ctx context.Context, id dssmodels.ID, owner dssmodels.Ow subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { old, err := repo.GetISA(ctx, id, true) switch { case err != nil: @@ -89,7 +88,7 @@ func (a *app) DeleteISA(ctx context.Context, id dssmodels.ID, owner dssmodels.Ow return stacktrace.Propagate(err, "Error updating notification indices") } return nil - })) + }) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } @@ -105,7 +104,7 @@ func (a *app) InsertISA(ctx context.Context, isa *ridmodels.IdentificationServic subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { // ensure it doesn't exist yet old, err := repo.GetISA(ctx, isa.ID, false) if err != nil { @@ -127,7 +126,7 @@ func (a *app) InsertISA(ctx context.Context, isa *ridmodels.IdentificationServic return stacktrace.Propagate(err, "Error inserting ISA") } return nil - })) + }) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } @@ -139,7 +138,7 @@ func (a *app) UpdateISA(ctx context.Context, isa *ridmodels.IdentificationServic subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { var err error old, err := repo.GetISA(ctx, isa.ID, true) @@ -177,7 +176,7 @@ func (a *app) UpdateISA(ctx context.Context, isa *ridmodels.IdentificationServic return stacktrace.Propagate(err, "Error updating notification indices") } return nil - })) + }) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } diff --git a/pkg/rid/application/subscription.go b/pkg/rid/application/subscription.go index b4bb91b75..abd7c8faf 100644 --- a/pkg/rid/application/subscription.go +++ b/pkg/rid/application/subscription.go @@ -8,7 +8,6 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -61,7 +60,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) return nil, stacktrace.Propagate(err, "Unable to adjust time range") } var sub *ridmodels.Subscription - _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { // ensure it doesn't exist yet old, err := repo.GetSubscription(ctx, s.ID) @@ -91,7 +90,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) } return nil - })) + }) return sub, err } @@ -99,7 +98,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) func (a *app) UpdateSubscription(ctx context.Context, s *ridmodels.Subscription) (*ridmodels.Subscription, error) { var sub *ridmodels.Subscription - _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { old, err := repo.GetSubscription(ctx, s.ID) switch { case err != nil: @@ -139,14 +138,14 @@ func (a *app) UpdateSubscription(ctx context.Context, s *ridmodels.Subscription) return stacktrace.Propagate(err, "Error updating Subscription in repo") } return nil - })) + }) return sub, err } // DeleteSubscription deletes the Subscription identified by "id" and owned by "owner". func (a *app) DeleteSubscription(ctx context.Context, id dssmodels.ID, owner dssmodels.Owner, version *dssmodels.Version) (*ridmodels.Subscription, error) { var ret *ridmodels.Subscription - _, err := a.store.Transact(ctx, store.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { var err error old, err := repo.GetSubscription(ctx, id) switch { @@ -169,6 +168,6 @@ func (a *app) DeleteSubscription(ctx context.Context, id dssmodels.ID, owner dss return stacktrace.Propagate(err, "Error deleting Subscription from repo") } return nil - })) + }) return ret, err } diff --git a/pkg/rid/store/sqlstore/store.go b/pkg/rid/store/sqlstore/store.go index 90bb20325..6579521a2 100644 --- a/pkg/rid/store/sqlstore/store.go +++ b/pkg/rid/store/sqlstore/store.go @@ -6,6 +6,7 @@ import ( dssql "github.com/interuss/dss/pkg/sql" "github.com/interuss/dss/pkg/logging" + "github.com/interuss/dss/pkg/rid/actions" "github.com/interuss/dss/pkg/rid/repos" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/store/params" @@ -44,5 +45,6 @@ func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (*sqlstor timeBasedNotificationIndex: opts.TimeBasedNotificationIndex, } }, + Dispatch: actions.Dispatch, }, withCheckCron) } diff --git a/pkg/rid/store/sqlstore/store_test.go b/pkg/rid/store/sqlstore/store_test.go index e6de252c5..0ea9e857f 100644 --- a/pkg/rid/store/sqlstore/store_test.go +++ b/pkg/rid/store/sqlstore/store_test.go @@ -13,7 +13,6 @@ import ( "github.com/interuss/dss/pkg/rid/repos" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/sqlstore/params" - dssstore "github.com/interuss/dss/pkg/store" "github.com/jackc/pgx/v5/pgconn" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" @@ -98,12 +97,12 @@ func TestTxnRetrier(t *testing.T) { require.NotNil(t, store) defer tearDownStore() - _, err := store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err := store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { // can query within this isa, err := repo.InsertISA(ctx, serviceArea) require.NotNil(t, isa) return err - })) + }) require.NoError(t, err) // can query afterwads repo, err := store.Interact(ctx) @@ -119,12 +118,12 @@ func TestTxnRetrier(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 20*time.Millisecond) defer cancel() count := 0 - _, err = store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, repo repos.Repository) error { + _, err = store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { // can query within this count++ // Postgre retryable error return &pgconn.PgError{Code: "40001"} - })) + }) require.Error(t, err) // Ensure it was retried. require.Greater(t, count, 1) @@ -142,13 +141,13 @@ func TestTransactor(t *testing.T) { subscription2 := subscriptionsPool[1].input txnCount := 0 - _, err := store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, s1 repos.Repository) error { + _, err := store.Transact(ctx, func(ctx context.Context, s1 repos.Repository) error { // We should get to this retry, then return nothing. if txnCount > 0 { return errors.New("already failed") } txnCount++ - _, err := store.Transact(ctx, dssstore.NewActionFunction(func(ctx context.Context, s2 repos.Repository) error { + _, err := store.Transact(ctx, func(ctx context.Context, s2 repos.Repository) error { subs, err := s1.SearchSubscriptions(ctx, subscription1.Cells) require.NoError(t, err) require.Len(t, subs, 0) @@ -165,9 +164,9 @@ func TestTransactor(t *testing.T) { require.NoError(t, err) return nil - })) + }) return err - })) + }) require.Error(t, err) repo, err := store.Interact(ctx) diff --git a/pkg/scd/actions/dispatch.go b/pkg/scd/actions/dispatch.go new file mode 100644 index 000000000..491994298 --- /dev/null +++ b/pkg/scd/actions/dispatch.go @@ -0,0 +1,13 @@ +package actions + +import ( + "context" + + "github.com/interuss/dss/pkg/scd/repos" + "github.com/interuss/stacktrace" +) + +// Dispatch routes a restapi operation to the appropriate execution function. +func Dispatch(_ context.Context, _ repos.Repository, op any) (any, error) { + return nil, stacktrace.NewError("dispatch not implemented for op %T", op) +} diff --git a/pkg/scd/constraints_handler.go b/pkg/scd/constraints_handler.go index de53cdd2d..baf0f7014 100644 --- a/pkg/scd/constraints_handler.go +++ b/pkg/scd/constraints_handler.go @@ -11,7 +11,6 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jackc/pgx/v5" ) @@ -89,7 +88,7 @@ func (a *Server) DeleteConstraintReference(ctx context.Context, req *restapi.Del return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not delete constraint") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -148,7 +147,7 @@ func (a *Server) GetConstraintReference(ctx context.Context, req *restapi.GetCon return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not get constraint") if stacktrace.GetCode(err) == dsserr.NotFound { @@ -307,7 +306,7 @@ func (a *Server) PutConstraintReference(ctx context.Context, manager string, ent return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { return nil, err // No need to Propagate this error as this is not a useful stacktrace line } @@ -435,7 +434,7 @@ func (a *Server) QueryConstraintReferences(ctx context.Context, req *restapi.Que return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { return restapi.QueryConstraintReferencesResponseSet{Response500: &api.InternalServerErrorBody{ ErrorMessage: *dsserr.Handle(ctx, stacktrace.Propagate(err, "Got an unexpected error"))}} diff --git a/pkg/scd/operational_intents_handler.go b/pkg/scd/operational_intents_handler.go index 6ad1a9f86..ad113cd5e 100644 --- a/pkg/scd/operational_intents_handler.go +++ b/pkg/scd/operational_intents_handler.go @@ -13,7 +13,6 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" ) @@ -166,7 +165,7 @@ func (a *Server) DeleteOperationalIntentReference(ctx context.Context, req *rest return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not delete operational intent") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -222,7 +221,7 @@ func (a *Server) GetOperationalIntentReference(ctx context.Context, req *restapi return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not get operational intent") if stacktrace.GetCode(err) == dsserr.NotFound { @@ -289,7 +288,7 @@ func (a *Server) QueryOperationalIntentReferences(ctx context.Context, req *rest return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not query operational intent") if stacktrace.GetCode(err) == dsserr.BadRequest { @@ -935,7 +934,7 @@ func (a *Server) upsertOperationalIntentReference(ctx context.Context, now time. return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { return nil, responseConflict, err // No need to Propagate this error as this is not a useful stacktrace line } diff --git a/pkg/scd/store/sqlstore/store.go b/pkg/scd/store/sqlstore/store.go index 54b509720..b88d65607 100644 --- a/pkg/scd/store/sqlstore/store.go +++ b/pkg/scd/store/sqlstore/store.go @@ -6,6 +6,7 @@ import ( dssql "github.com/interuss/dss/pkg/sql" "github.com/interuss/dss/pkg/logging" + "github.com/interuss/dss/pkg/scd/actions" "github.com/interuss/dss/pkg/scd/repos" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/store/params" @@ -46,5 +47,6 @@ func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (*sqlstor timeBasedNotificationIndex: opts.TimeBasedNotificationIndex, } }, + Dispatch: actions.Dispatch, }, withCheckCron) } diff --git a/pkg/scd/subscriptions_handler.go b/pkg/scd/subscriptions_handler.go index c01a5e266..b12e65001 100644 --- a/pkg/scd/subscriptions_handler.go +++ b/pkg/scd/subscriptions_handler.go @@ -12,7 +12,6 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jonboulle/clockwork" ) @@ -277,7 +276,7 @@ func (a *Server) PutSubscription(ctx context.Context, manager string, subscripti return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { return nil, err // No need to Propagate this error as this is not a useful stacktrace line } @@ -341,7 +340,7 @@ func (a *Server) GetSubscription(ctx context.Context, req *restapi.GetSubscripti return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not get subscription") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -425,7 +424,7 @@ func (a *Server) QuerySubscriptions(ctx context.Context, req *restapi.QuerySubsc return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -518,7 +517,7 @@ func (a *Server) DeleteSubscription(ctx context.Context, req *restapi.DeleteSubs return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { err = stacktrace.Propagate(err, "Could not delete subscription") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} diff --git a/pkg/scd/uss_availability_handler.go b/pkg/scd/uss_availability_handler.go index 3d5857f5f..0fd684f69 100644 --- a/pkg/scd/uss_availability_handler.go +++ b/pkg/scd/uss_availability_handler.go @@ -10,7 +10,6 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" - "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jackc/pgx/v5" ) @@ -52,7 +51,7 @@ func (a *Server) GetUssAvailability(ctx context.Context, req *restapi.GetUssAvai return nil } - _, err := a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err := a.Store.Transact(ctx, action) if err != nil { // In case of older DB versions where availability table doesn't exist if strings.Contains(err.Error(), "does not exist") { @@ -122,7 +121,7 @@ func (a *Server) SetUssAvailability(ctx context.Context, req *restapi.SetUssAvai } return nil } - _, err = a.Store.Transact(ctx, store.NewActionFunction(action)) + _, err = a.Store.Transact(ctx, action) if err != nil { // In case of older DB versions where availability table doesn't exist if strings.Contains(err.Error(), "does not exist") { diff --git a/pkg/sqlstore/store.go b/pkg/sqlstore/store.go index d5a213916..b7f934863 100644 --- a/pkg/sqlstore/store.go +++ b/pkg/sqlstore/store.go @@ -35,6 +35,7 @@ type Store[R any] struct { Clock clockwork.Clock schemaVersion *semver.Version newRepo func(dsssql.Queryable) R + dispatch func(context.Context, R, any) (any, error) maxRetries int } @@ -45,6 +46,7 @@ type Config[R any] struct { CrdbMajorSchemaVersion int64 YbMajorSchemaVersion int64 NewRepo func(q dsssql.Queryable, clock clockwork.Clock, v *Version) R + Dispatch func(context.Context, R, any) (any, error) } func checkMajorSchemaVersion[R any](ctx context.Context, db *Store[R], vs *semver.Version, crdbExpected int64, ybExpected int64) error { @@ -155,6 +157,7 @@ func Init[R any](ctx context.Context, cfg Config[R], withCheckCron bool) (*Store db.newRepo = func(q dsssql.Queryable) R { return cfg.NewRepo(q, db.Clock, db.Version) } + db.dispatch = cfg.Dispatch if withCheckCron { c := cron.New() @@ -176,27 +179,40 @@ func (s *Store[R]) Interact(_ context.Context) (R, error) { return s.newRepo(s.Pool), nil } -func (s *Store[R]) Transact(ctx context.Context, action store.Action[R]) (any, error) { +func (s *Store[R]) execute(ctx context.Context, repo R, op any) (any, error) { + // TODO: This is a temporary fallback until Dispatch is implemented for all operations. + if f, ok := op.(func(context.Context, R) error); ok { + return nil, f(ctx, repo) + } + + if s.dispatch != nil { + return s.dispatch(ctx, repo, op) + } + + return nil, stacktrace.NewError("failed to dispatch or execute op %T", op) +} + +func (s *Store[R]) Transact(ctx context.Context, op any) (any, error) { if s.Version.Type == Yugabyte { - return s.transactYugabyte(ctx, action) + return s.transactYugabyte(ctx, op) } ctx = crdb.WithMaxRetries(ctx, s.maxRetries) var result any var err error err = crdbpgx.ExecuteTx(ctx, s.Pool, pgx.TxOptions{IsoLevel: pgx.Serializable}, func(tx pgx.Tx) error { - result, err = action.Execute(ctx, s.newRepo(tx)) + result, err = s.execute(ctx, s.newRepo(tx), op) return err }) return result, err } -func (s *Store[R]) transactYugabyte(ctx context.Context, action store.Action[R]) (any, error) { +func (s *Store[R]) transactYugabyte(ctx context.Context, op any) (any, error) { var result any var err error for attempt := 0; attempt <= s.maxRetries; attempt++ { err = pgx.BeginTxFunc(ctx, s.Pool, pgx.TxOptions{IsoLevel: pgx.Serializable}, func(tx pgx.Tx) error { - result, err = action.Execute(ctx, s.newRepo(tx)) + result, err = s.execute(ctx, s.newRepo(tx), op) return err }) if err == nil || !isYugabyteRetryable(err) { diff --git a/pkg/store/store.go b/pkg/store/store.go index f163b2ba6..8dc4613ed 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -7,18 +7,6 @@ import ( "github.com/interuss/stacktrace" ) -// Action represents a set of operations to be performed on a Repo type R. -type Action[R any] interface { - ActionMetadata - Execute(ctx context.Context, r R) (any, error) - Payload() any -} - -type ActionMetadata interface { - RequestType() string - IsReadOnly() bool -} - // store.Store is the generic means to access and interact with any type of data backing the DSS // may ever use, by obtaining a means to perform R-specific (repo type) operations. type Store[R any] interface { @@ -26,60 +14,24 @@ type Store[R any] interface { // Obtain a Repo (repo type R) that doesn't need transactional guarantees (for instance, // read-only). Interact(context.Context) (R, error) - // Attempt to apply the operations in action to the R Repo it is supplied. All operations performed - // on the R Repo by action will be applied or rejected atomically. - Transact(ctx context.Context, action Action[R]) (any, error) + // Attempt to apply the operation op to the R Repo atomically. + Transact(ctx context.Context, op any) (any, error) } -// TransactWithResult wraps Store.Transact and returns the result of the action as a specific type, or an error if the action failed or returned a result of a different type. -// We use this to avoid having to cast the result of Transact in every handler. -func TransactWithResult[R any, Res any](ctx context.Context, s Store[R], action Action[R]) (Res, error) { - var emptyResult Res - result, err := s.Transact(ctx, action) +// TransactWithResult wraps Store.Transact and casts the result to ResultType, avoiding a cast at every call site. +func TransactWithResult[R any, ResultType any](ctx context.Context, s Store[R], op any) (ResultType, error) { + var emptyResult ResultType + result, err := s.Transact(ctx, op) if err != nil { return emptyResult, err } - res, ok := result.(Res) + res, ok := result.(ResultType) if !ok { return emptyResult, stacktrace.NewError("unexpected result type %T, want %T", result, emptyResult) } return res, nil } -type ActionAdapter[R any, T ActionMetadata] struct { - Data T - Run func(ctx context.Context, r R, data T) (any, error) -} - -func (b *ActionAdapter[R, T]) RequestType() string { return b.Data.RequestType() } - -func (b *ActionAdapter[R, T]) IsReadOnly() bool { return b.Data.IsReadOnly() } - -func (b *ActionAdapter[R, T]) Payload() any { return b.Data } - -func (b *ActionAdapter[R, T]) Execute(ctx context.Context, r R) (any, error) { - return b.Run(ctx, r, b.Data) -} - -// TODO: This is a placeholder struct that needs to be removed once all handlers are converted into Actions -type ActionFunction[R any] struct { - f func(context.Context, R) error -} - -func NewActionFunction[R any](f func(context.Context, R) error) *ActionFunction[R] { - return &ActionFunction[R]{f: f} -} - -func (a *ActionFunction[R]) RequestType() string { return "" } - -func (a *ActionFunction[R]) IsReadOnly() bool { return false } - -func (a *ActionFunction[R]) Payload() any { return nil } - -func (a *ActionFunction[R]) Execute(ctx context.Context, r R) (any, error) { - return nil, a.f(ctx, r) -} - const ( CodeRetryable = stacktrace.ErrorCode(1) ) From becf1dd92013448a7ce19bc6cb07b3d1ada0f25d Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Wed, 8 Jul 2026 12:06:29 +0200 Subject: [PATCH 3/7] implement new design --- cmds/db-manager/cleanup/evict.go | 5 +-- pkg/aux_/actions/dispatch.go | 13 ------- pkg/aux_/actions/registry.go | 10 ++++++ pkg/aux_/store/sqlstore/store.go | 2 +- pkg/memstore/store.go | 3 +- pkg/raftstore/store.go | 3 +- pkg/rid/actions/dispatch.go | 13 ------- pkg/rid/actions/registry.go | 10 ++++++ pkg/rid/application/application_test.go | 8 ++--- pkg/rid/application/isa.go | 13 +++---- pkg/rid/application/subscription.go | 13 +++---- pkg/rid/store/sqlstore/store.go | 2 +- pkg/rid/store/sqlstore/store_test.go | 17 +++++----- pkg/scd/actions/dispatch.go | 13 ------- pkg/scd/actions/registry.go | 10 ++++++ pkg/scd/constraints_handler.go | 9 ++--- pkg/scd/operational_intents_handler.go | 9 ++--- pkg/scd/store/sqlstore/store.go | 2 +- pkg/scd/subscriptions_handler.go | 9 ++--- pkg/scd/uss_availability_handler.go | 5 +-- pkg/sqlstore/store.go | 31 +++++++++-------- pkg/store/store.go | 45 +++++++++++++++++++++---- 22 files changed, 139 insertions(+), 106 deletions(-) delete mode 100644 pkg/aux_/actions/dispatch.go create mode 100644 pkg/aux_/actions/registry.go delete mode 100644 pkg/rid/actions/dispatch.go create mode 100644 pkg/rid/actions/registry.go delete mode 100644 pkg/scd/actions/dispatch.go create mode 100644 pkg/scd/actions/registry.go diff --git a/cmds/db-manager/cleanup/evict.go b/cmds/db-manager/cleanup/evict.go index e50f047fa..efb927d53 100644 --- a/cmds/db-manager/cleanup/evict.go +++ b/cmds/db-manager/cleanup/evict.go @@ -11,6 +11,7 @@ import ( ridmodels "github.com/interuss/dss/pkg/rid/models" ridrepos "github.com/interuss/dss/pkg/rid/repos" rids "github.com/interuss/dss/pkg/rid/store" + dssstore "github.com/interuss/dss/pkg/store" scdmodels "github.com/interuss/dss/pkg/scd/models" scdrepos "github.com/interuss/dss/pkg/scd/repos" scds "github.com/interuss/dss/pkg/scd/store" @@ -100,7 +101,7 @@ func evict(cmd *cobra.Command, _ []string) error { } return nil } - if _, err = scdStore.Transact(ctx, scdAction); err != nil { + if _, err = scdStore.Transact(ctx, dssstore.NewFuncOperation(scdAction)); err != nil { return fmt.Errorf("failed to execute SCD transaction: %w", err) } @@ -145,7 +146,7 @@ func evict(cmd *cobra.Command, _ []string) error { return nil } - if _, err = ridStore.Transact(ctx, ridAction); err != nil { + if _, err = ridStore.Transact(ctx, dssstore.NewFuncOperation(ridAction)); err != nil { return fmt.Errorf("failed to execute RID transaction: %w", err) } diff --git a/pkg/aux_/actions/dispatch.go b/pkg/aux_/actions/dispatch.go deleted file mode 100644 index c862ee96f..000000000 --- a/pkg/aux_/actions/dispatch.go +++ /dev/null @@ -1,13 +0,0 @@ -package actions - -import ( - "context" - - "github.com/interuss/dss/pkg/aux_/repos" - "github.com/interuss/stacktrace" -) - -// Dispatch routes a restapi operation to the appropriate execution function. -func Dispatch(_ context.Context, _ repos.Repository, op any) (any, error) { - return nil, stacktrace.NewError("dispatch not implemented for op %T", op) -} diff --git a/pkg/aux_/actions/registry.go b/pkg/aux_/actions/registry.go new file mode 100644 index 000000000..d16395b88 --- /dev/null +++ b/pkg/aux_/actions/registry.go @@ -0,0 +1,10 @@ +package actions + +import ( + "github.com/interuss/dss/pkg/aux_/repos" + dssstore "github.com/interuss/dss/pkg/store" +) + +// Registry maps operation IDs to their handlers. +// TODO: implement +var Registry = map[string]dssstore.OperationHandler[repos.Repository]{} diff --git a/pkg/aux_/store/sqlstore/store.go b/pkg/aux_/store/sqlstore/store.go index c7dace41f..772c3e4fd 100644 --- a/pkg/aux_/store/sqlstore/store.go +++ b/pkg/aux_/store/sqlstore/store.go @@ -40,6 +40,6 @@ func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (*sqlstor version: version, } }, - Dispatch: actions.Dispatch, + Registry: actions.Registry, }, withCheckCron) } diff --git a/pkg/memstore/store.go b/pkg/memstore/store.go index ee1bde05e..026e3c2c5 100644 --- a/pkg/memstore/store.go +++ b/pkg/memstore/store.go @@ -12,6 +12,7 @@ import ( dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/logging" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -50,7 +51,7 @@ func Init[R any](ctx context.Context, logger *zap.Logger, name string, r MemRepo return store, nil } -func (s *Store[R]) Transact(ctx context.Context, _ any) (any, error) { +func (s *Store[R]) Transact(ctx context.Context, _ store.OperationRequest) (any, error) { return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "Transact not implemented for memstore") } diff --git a/pkg/raftstore/store.go b/pkg/raftstore/store.go index 8fd3c6a54..12bdb78bd 100644 --- a/pkg/raftstore/store.go +++ b/pkg/raftstore/store.go @@ -5,6 +5,7 @@ import ( "github.com/interuss/dss/pkg/raftstore/consensus" raftparams "github.com/interuss/dss/pkg/raftstore/params" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -29,7 +30,7 @@ func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.Conn } // Transact proposes the entry to Raft and blocks until it is committed and applied. -func (s *Store[R]) Transact(_ context.Context, _ any) (any, error) { +func (s *Store[R]) Transact(_ context.Context, _ store.OperationRequest) (any, error) { // TODO: implement return nil, nil } diff --git a/pkg/rid/actions/dispatch.go b/pkg/rid/actions/dispatch.go deleted file mode 100644 index 15c3e71cc..000000000 --- a/pkg/rid/actions/dispatch.go +++ /dev/null @@ -1,13 +0,0 @@ -package actions - -import ( - "context" - - "github.com/interuss/dss/pkg/rid/repos" - "github.com/interuss/stacktrace" -) - -// Dispatch routes a restapi operation to the appropriate execution function. -func Dispatch(_ context.Context, _ repos.Repository, op any) (any, error) { - return nil, stacktrace.NewError("dispatch not implemented for op %T", op) -} diff --git a/pkg/rid/actions/registry.go b/pkg/rid/actions/registry.go new file mode 100644 index 000000000..82c125a55 --- /dev/null +++ b/pkg/rid/actions/registry.go @@ -0,0 +1,10 @@ +package actions + +import ( + "github.com/interuss/dss/pkg/rid/repos" + dssstore "github.com/interuss/dss/pkg/store" +) + +// Registry maps operation IDs to their handlers. +// TODO: implement +var Registry = map[string]dssstore.OperationHandler[repos.Repository]{} diff --git a/pkg/rid/application/application_test.go b/pkg/rid/application/application_test.go index 93c754f99..929c0e507 100644 --- a/pkg/rid/application/application_test.go +++ b/pkg/rid/application/application_test.go @@ -13,6 +13,7 @@ import ( dssql "github.com/interuss/dss/pkg/sql" "github.com/interuss/dss/pkg/sqlstore" "github.com/interuss/dss/pkg/sqlstore/params" + dssstore "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" @@ -36,10 +37,9 @@ func (s *mockRepo) Interact(ctx context.Context) (repos.Repository, error) { return s, nil } -func (s *mockRepo) Transact(ctx context.Context, op any) (any, error) { - // TODO: update this when Dispatch is implemented for rid operations. - if f, ok := op.(func(context.Context, repos.Repository) error); ok { - return nil, f(ctx, s) +func (s *mockRepo) Transact(ctx context.Context, op dssstore.OperationRequest) (any, error) { + if fn, ok := op.(*dssstore.FuncOperation[repos.Repository]); ok { + return nil, fn.Execute(ctx, s) } return nil, stacktrace.NewError("unsupported operation type %T", op) } diff --git a/pkg/rid/application/isa.go b/pkg/rid/application/isa.go index 11bba7654..a84170a4f 100644 --- a/pkg/rid/application/isa.go +++ b/pkg/rid/application/isa.go @@ -10,6 +10,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" ) @@ -63,7 +64,7 @@ func (a *app) DeleteISA(ctx context.Context, id dssmodels.ID, owner dssmodels.Ow subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { old, err := repo.GetISA(ctx, id, true) switch { case err != nil: @@ -88,7 +89,7 @@ func (a *app) DeleteISA(ctx context.Context, id dssmodels.ID, owner dssmodels.Ow return stacktrace.Propagate(err, "Error updating notification indices") } return nil - }) + })) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } @@ -104,7 +105,7 @@ func (a *app) InsertISA(ctx context.Context, isa *ridmodels.IdentificationServic subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { // ensure it doesn't exist yet old, err := repo.GetISA(ctx, isa.ID, false) if err != nil { @@ -126,7 +127,7 @@ func (a *app) InsertISA(ctx context.Context, isa *ridmodels.IdentificationServic return stacktrace.Propagate(err, "Error inserting ISA") } return nil - }) + })) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } @@ -138,7 +139,7 @@ func (a *app) UpdateISA(ctx context.Context, isa *ridmodels.IdentificationServic subs []*ridmodels.Subscription ) // The following will automatically retry TXN retry errors. - _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { var err error old, err := repo.GetISA(ctx, isa.ID, true) @@ -176,7 +177,7 @@ func (a *app) UpdateISA(ctx context.Context, isa *ridmodels.IdentificationServic return stacktrace.Propagate(err, "Error updating notification indices") } return nil - }) + })) return ret, subs, err // No need to Propagate this error as this stack layer does not add useful information } diff --git a/pkg/rid/application/subscription.go b/pkg/rid/application/subscription.go index abd7c8faf..2e666adeb 100644 --- a/pkg/rid/application/subscription.go +++ b/pkg/rid/application/subscription.go @@ -8,6 +8,7 @@ import ( dssmodels "github.com/interuss/dss/pkg/models" ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -60,7 +61,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) return nil, stacktrace.Propagate(err, "Unable to adjust time range") } var sub *ridmodels.Subscription - _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { // ensure it doesn't exist yet old, err := repo.GetSubscription(ctx, s.ID) @@ -90,7 +91,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) } return nil - }) + })) return sub, err } @@ -98,7 +99,7 @@ func (a *app) InsertSubscription(ctx context.Context, s *ridmodels.Subscription) func (a *app) UpdateSubscription(ctx context.Context, s *ridmodels.Subscription) (*ridmodels.Subscription, error) { var sub *ridmodels.Subscription - _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { old, err := repo.GetSubscription(ctx, s.ID) switch { case err != nil: @@ -138,14 +139,14 @@ func (a *app) UpdateSubscription(ctx context.Context, s *ridmodels.Subscription) return stacktrace.Propagate(err, "Error updating Subscription in repo") } return nil - }) + })) return sub, err } // DeleteSubscription deletes the Subscription identified by "id" and owned by "owner". func (a *app) DeleteSubscription(ctx context.Context, id dssmodels.ID, owner dssmodels.Owner, version *dssmodels.Version) (*ridmodels.Subscription, error) { var ret *ridmodels.Subscription - _, err := a.store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := a.store.Transact(ctx, store.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { var err error old, err := repo.GetSubscription(ctx, id) switch { @@ -168,6 +169,6 @@ func (a *app) DeleteSubscription(ctx context.Context, id dssmodels.ID, owner dss return stacktrace.Propagate(err, "Error deleting Subscription from repo") } return nil - }) + })) return ret, err } diff --git a/pkg/rid/store/sqlstore/store.go b/pkg/rid/store/sqlstore/store.go index 6579521a2..20bb2e026 100644 --- a/pkg/rid/store/sqlstore/store.go +++ b/pkg/rid/store/sqlstore/store.go @@ -45,6 +45,6 @@ func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (*sqlstor timeBasedNotificationIndex: opts.TimeBasedNotificationIndex, } }, - Dispatch: actions.Dispatch, + Registry: actions.Registry, }, withCheckCron) } diff --git a/pkg/rid/store/sqlstore/store_test.go b/pkg/rid/store/sqlstore/store_test.go index 0ea9e857f..6adb31d24 100644 --- a/pkg/rid/store/sqlstore/store_test.go +++ b/pkg/rid/store/sqlstore/store_test.go @@ -12,6 +12,7 @@ import ( ridmodels "github.com/interuss/dss/pkg/rid/models" "github.com/interuss/dss/pkg/rid/repos" "github.com/interuss/dss/pkg/sqlstore" + dssstore "github.com/interuss/dss/pkg/store" "github.com/interuss/dss/pkg/sqlstore/params" "github.com/jackc/pgx/v5/pgconn" "github.com/jonboulle/clockwork" @@ -97,12 +98,12 @@ func TestTxnRetrier(t *testing.T) { require.NotNil(t, store) defer tearDownStore() - _, err := store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err := store.Transact(ctx, dssstore.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { // can query within this isa, err := repo.InsertISA(ctx, serviceArea) require.NotNil(t, isa) return err - }) + })) require.NoError(t, err) // can query afterwads repo, err := store.Interact(ctx) @@ -118,12 +119,12 @@ func TestTxnRetrier(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 20*time.Millisecond) defer cancel() count := 0 - _, err = store.Transact(ctx, func(ctx context.Context, repo repos.Repository) error { + _, err = store.Transact(ctx, dssstore.NewFuncOperation(func(ctx context.Context, repo repos.Repository) error { // can query within this count++ // Postgre retryable error return &pgconn.PgError{Code: "40001"} - }) + })) require.Error(t, err) // Ensure it was retried. require.Greater(t, count, 1) @@ -141,13 +142,13 @@ func TestTransactor(t *testing.T) { subscription2 := subscriptionsPool[1].input txnCount := 0 - _, err := store.Transact(ctx, func(ctx context.Context, s1 repos.Repository) error { + _, err := store.Transact(ctx, dssstore.NewFuncOperation(func(ctx context.Context, s1 repos.Repository) error { // We should get to this retry, then return nothing. if txnCount > 0 { return errors.New("already failed") } txnCount++ - _, err := store.Transact(ctx, func(ctx context.Context, s2 repos.Repository) error { + _, err := store.Transact(ctx, dssstore.NewFuncOperation(func(ctx context.Context, s2 repos.Repository) error { subs, err := s1.SearchSubscriptions(ctx, subscription1.Cells) require.NoError(t, err) require.Len(t, subs, 0) @@ -164,9 +165,9 @@ func TestTransactor(t *testing.T) { require.NoError(t, err) return nil - }) + })) return err - }) + })) require.Error(t, err) repo, err := store.Interact(ctx) diff --git a/pkg/scd/actions/dispatch.go b/pkg/scd/actions/dispatch.go deleted file mode 100644 index 491994298..000000000 --- a/pkg/scd/actions/dispatch.go +++ /dev/null @@ -1,13 +0,0 @@ -package actions - -import ( - "context" - - "github.com/interuss/dss/pkg/scd/repos" - "github.com/interuss/stacktrace" -) - -// Dispatch routes a restapi operation to the appropriate execution function. -func Dispatch(_ context.Context, _ repos.Repository, op any) (any, error) { - return nil, stacktrace.NewError("dispatch not implemented for op %T", op) -} diff --git a/pkg/scd/actions/registry.go b/pkg/scd/actions/registry.go new file mode 100644 index 000000000..c8389cc26 --- /dev/null +++ b/pkg/scd/actions/registry.go @@ -0,0 +1,10 @@ +package actions + +import ( + "github.com/interuss/dss/pkg/scd/repos" + dssstore "github.com/interuss/dss/pkg/store" +) + +// Registry maps operation IDs to their handlers +// TODO: implement +var Registry = map[string]dssstore.OperationHandler[repos.Repository]{} diff --git a/pkg/scd/constraints_handler.go b/pkg/scd/constraints_handler.go index baf0f7014..19a71f852 100644 --- a/pkg/scd/constraints_handler.go +++ b/pkg/scd/constraints_handler.go @@ -8,6 +8,7 @@ import ( "github.com/interuss/dss/pkg/api" restapi "github.com/interuss/dss/pkg/api/scdv1" dsserr "github.com/interuss/dss/pkg/errors" + dssstore "github.com/interuss/dss/pkg/store" dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" @@ -88,7 +89,7 @@ func (a *Server) DeleteConstraintReference(ctx context.Context, req *restapi.Del return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not delete constraint") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -147,7 +148,7 @@ func (a *Server) GetConstraintReference(ctx context.Context, req *restapi.GetCon return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not get constraint") if stacktrace.GetCode(err) == dsserr.NotFound { @@ -306,7 +307,7 @@ func (a *Server) PutConstraintReference(ctx context.Context, manager string, ent return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { return nil, err // No need to Propagate this error as this is not a useful stacktrace line } @@ -434,7 +435,7 @@ func (a *Server) QueryConstraintReferences(ctx context.Context, req *restapi.Que return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { return restapi.QueryConstraintReferencesResponseSet{Response500: &api.InternalServerErrorBody{ ErrorMessage: *dsserr.Handle(ctx, stacktrace.Propagate(err, "Got an unexpected error"))}} diff --git a/pkg/scd/operational_intents_handler.go b/pkg/scd/operational_intents_handler.go index ad113cd5e..4d1203229 100644 --- a/pkg/scd/operational_intents_handler.go +++ b/pkg/scd/operational_intents_handler.go @@ -10,6 +10,7 @@ import ( restapi "github.com/interuss/dss/pkg/api/scdv1" "github.com/interuss/dss/pkg/auth" dsserr "github.com/interuss/dss/pkg/errors" + dssstore "github.com/interuss/dss/pkg/store" dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" @@ -165,7 +166,7 @@ func (a *Server) DeleteOperationalIntentReference(ctx context.Context, req *rest return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not delete operational intent") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -221,7 +222,7 @@ func (a *Server) GetOperationalIntentReference(ctx context.Context, req *restapi return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not get operational intent") if stacktrace.GetCode(err) == dsserr.NotFound { @@ -288,7 +289,7 @@ func (a *Server) QueryOperationalIntentReferences(ctx context.Context, req *rest return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not query operational intent") if stacktrace.GetCode(err) == dsserr.BadRequest { @@ -934,7 +935,7 @@ func (a *Server) upsertOperationalIntentReference(ctx context.Context, now time. return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { return nil, responseConflict, err // No need to Propagate this error as this is not a useful stacktrace line } diff --git a/pkg/scd/store/sqlstore/store.go b/pkg/scd/store/sqlstore/store.go index b88d65607..3c2bb8f54 100644 --- a/pkg/scd/store/sqlstore/store.go +++ b/pkg/scd/store/sqlstore/store.go @@ -47,6 +47,6 @@ func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (*sqlstor timeBasedNotificationIndex: opts.TimeBasedNotificationIndex, } }, - Dispatch: actions.Dispatch, + Registry: actions.Registry, }, withCheckCron) } diff --git a/pkg/scd/subscriptions_handler.go b/pkg/scd/subscriptions_handler.go index b12e65001..a0befdd18 100644 --- a/pkg/scd/subscriptions_handler.go +++ b/pkg/scd/subscriptions_handler.go @@ -8,6 +8,7 @@ import ( "github.com/interuss/dss/pkg/api" restapi "github.com/interuss/dss/pkg/api/scdv1" dsserr "github.com/interuss/dss/pkg/errors" + dssstore "github.com/interuss/dss/pkg/store" "github.com/interuss/dss/pkg/geo" dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" @@ -276,7 +277,7 @@ func (a *Server) PutSubscription(ctx context.Context, manager string, subscripti return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { return nil, err // No need to Propagate this error as this is not a useful stacktrace line } @@ -340,7 +341,7 @@ func (a *Server) GetSubscription(ctx context.Context, req *restapi.GetSubscripti return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not get subscription") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -424,7 +425,7 @@ func (a *Server) QuerySubscriptions(ctx context.Context, req *restapi.QuerySubsc return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} @@ -517,7 +518,7 @@ func (a *Server) DeleteSubscription(ctx context.Context, req *restapi.DeleteSubs return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { err = stacktrace.Propagate(err, "Could not delete subscription") errResp := &restapi.ErrorResponse{Message: dsserr.Handle(ctx, err)} diff --git a/pkg/scd/uss_availability_handler.go b/pkg/scd/uss_availability_handler.go index 0fd684f69..1e4969560 100644 --- a/pkg/scd/uss_availability_handler.go +++ b/pkg/scd/uss_availability_handler.go @@ -7,6 +7,7 @@ import ( "github.com/interuss/dss/pkg/api" restapi "github.com/interuss/dss/pkg/api/scdv1" dsserr "github.com/interuss/dss/pkg/errors" + dssstore "github.com/interuss/dss/pkg/store" dssmodels "github.com/interuss/dss/pkg/models" scdmodels "github.com/interuss/dss/pkg/scd/models" "github.com/interuss/dss/pkg/scd/repos" @@ -51,7 +52,7 @@ func (a *Server) GetUssAvailability(ctx context.Context, req *restapi.GetUssAvai return nil } - _, err := a.Store.Transact(ctx, action) + _, err := a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { // In case of older DB versions where availability table doesn't exist if strings.Contains(err.Error(), "does not exist") { @@ -121,7 +122,7 @@ func (a *Server) SetUssAvailability(ctx context.Context, req *restapi.SetUssAvai } return nil } - _, err = a.Store.Transact(ctx, action) + _, err = a.Store.Transact(ctx, dssstore.NewFuncOperation(action)) if err != nil { // In case of older DB versions where availability table doesn't exist if strings.Contains(err.Error(), "does not exist") { diff --git a/pkg/sqlstore/store.go b/pkg/sqlstore/store.go index b7f934863..674a1abd0 100644 --- a/pkg/sqlstore/store.go +++ b/pkg/sqlstore/store.go @@ -34,9 +34,9 @@ type Store[R any] struct { Version *Version Clock clockwork.Clock schemaVersion *semver.Version - newRepo func(dsssql.Queryable) R - dispatch func(context.Context, R, any) (any, error) - maxRetries int + newRepo func(dsssql.Queryable) R + registry map[string]store.OperationHandler[R] + maxRetries int } // Config describes everything a SQL-backed store needs to be initialized for a @@ -46,7 +46,7 @@ type Config[R any] struct { CrdbMajorSchemaVersion int64 YbMajorSchemaVersion int64 NewRepo func(q dsssql.Queryable, clock clockwork.Clock, v *Version) R - Dispatch func(context.Context, R, any) (any, error) + Registry map[string]store.OperationHandler[R] } func checkMajorSchemaVersion[R any](ctx context.Context, db *Store[R], vs *semver.Version, crdbExpected int64, ybExpected int64) error { @@ -157,7 +157,7 @@ func Init[R any](ctx context.Context, cfg Config[R], withCheckCron bool) (*Store db.newRepo = func(q dsssql.Queryable) R { return cfg.NewRepo(q, db.Clock, db.Version) } - db.dispatch = cfg.Dispatch + db.registry = cfg.Registry if withCheckCron { c := cron.New() @@ -179,20 +179,19 @@ func (s *Store[R]) Interact(_ context.Context) (R, error) { return s.newRepo(s.Pool), nil } -func (s *Store[R]) execute(ctx context.Context, repo R, op any) (any, error) { - // TODO: This is a temporary fallback until Dispatch is implemented for all operations. - if f, ok := op.(func(context.Context, R) error); ok { - return nil, f(ctx, repo) +func (s *Store[R]) execute(ctx context.Context, repo R, op store.OperationRequest) (any, error) { + // TODO: This should be removed once all operations are registered properly. + if fn, ok := op.(*store.FuncOperation[R]); ok { + return nil, fn.Execute(ctx, repo) } - - if s.dispatch != nil { - return s.dispatch(ctx, repo, op) + handler, ok := s.registry[op.OperationID()] + if !ok { + return nil, stacktrace.NewError("no handler registered for operation %q", op.OperationID()) } - - return nil, stacktrace.NewError("failed to dispatch or execute op %T", op) + return handler.Execute(ctx, repo, op) } -func (s *Store[R]) Transact(ctx context.Context, op any) (any, error) { +func (s *Store[R]) Transact(ctx context.Context, op store.OperationRequest) (any, error) { if s.Version.Type == Yugabyte { return s.transactYugabyte(ctx, op) } @@ -207,7 +206,7 @@ func (s *Store[R]) Transact(ctx context.Context, op any) (any, error) { return result, err } -func (s *Store[R]) transactYugabyte(ctx context.Context, op any) (any, error) { +func (s *Store[R]) transactYugabyte(ctx context.Context, op store.OperationRequest) (any, error) { var result any var err error for attempt := 0; attempt <= s.maxRetries; attempt++ { diff --git a/pkg/store/store.go b/pkg/store/store.go index 8dc4613ed..0dd2ecdff 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -2,36 +2,69 @@ package store import ( "context" + "encoding" "io" "github.com/interuss/stacktrace" ) +// OperationRequest represents a request to be performed on a Store. +type OperationRequest interface { + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + + OperationID() string +} + +// OperationHandler holds the business logic for a registered operation. +type OperationHandler[R any] struct { + New func() OperationRequest + Execute func(ctx context.Context, r R, req OperationRequest) (any, error) +} + // store.Store is the generic means to access and interact with any type of data backing the DSS -// may ever use, by obtaining a means to perform R-specific (repo type) operations. type Store[R any] interface { io.Closer // Obtain a Repo (repo type R) that doesn't need transactional guarantees (for instance, // read-only). Interact(context.Context) (R, error) // Attempt to apply the operation op to the R Repo atomically. - Transact(ctx context.Context, op any) (any, error) + Transact(ctx context.Context, op OperationRequest) (any, error) } // TransactWithResult wraps Store.Transact and casts the result to ResultType, avoiding a cast at every call site. -func TransactWithResult[R any, ResultType any](ctx context.Context, s Store[R], op any) (ResultType, error) { - var emptyResult ResultType +func TransactWithResult[R any, ResultType any](ctx context.Context, s Store[R], op OperationRequest) (ResultType, error) { + var zero ResultType result, err := s.Transact(ctx, op) if err != nil { - return emptyResult, err + return zero, err } res, ok := result.(ResultType) if !ok { - return emptyResult, stacktrace.NewError("unexpected result type %T, want %T", result, emptyResult) + return zero, stacktrace.NewError("unexpected result type %T, want %T", result, zero) } return res, nil } +// FuncOperation wraps a closure as an OperationRequest for gradual migration. +// TODO: remove once all handlers use registered operations. +type FuncOperation[R any] struct { + f func(context.Context, R) error +} + +func NewFuncOperation[R any](f func(context.Context, R) error) *FuncOperation[R] { + return &FuncOperation[R]{f: f} +} + +func (a *FuncOperation[R]) OperationID() string { return "" } +func (a *FuncOperation[R]) MarshalBinary() ([]byte, error) { + return nil, stacktrace.NewError("FuncOperation cannot be serialized") +} +func (a *FuncOperation[R]) UnmarshalBinary(_ []byte) error { + return stacktrace.NewError("FuncOperation cannot be deserialized") +} +func (a *FuncOperation[R]) Execute(ctx context.Context, r R) error { return a.f(ctx, r) } + const ( CodeRetryable = stacktrace.ErrorCode(1) ) From c302d13b9c2356ffd91509abff08aec1c392797f Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Fri, 22 May 2026 09:23:39 +0200 Subject: [PATCH 4/7] [raft/store] Add generic store --- pkg/aux_/store/raftstore/store.go | 18 +++++- pkg/raftstore/consensus/proposal.go | 9 ++- pkg/raftstore/store.go | 94 ++++++++++++++++++++++++----- pkg/rid/store/raftstore/store.go | 18 +++++- pkg/scd/store/raftstore/store.go | 18 +++++- 5 files changed, 136 insertions(+), 21 deletions(-) diff --git a/pkg/aux_/store/raftstore/store.go b/pkg/aux_/store/raftstore/store.go index d09162d87..eb9be35de 100644 --- a/pkg/aux_/store/raftstore/store.go +++ b/pkg/aux_/store/raftstore/store.go @@ -5,7 +5,9 @@ import ( "github.com/interuss/dss/pkg/aux_/repos" auxraftparams "github.com/interuss/dss/pkg/aux_/store/raftstore/params" + dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/raftstore" + "github.com/interuss/dss/pkg/raftstore/consensus" "github.com/interuss/stacktrace" "go.uber.org/zap" ) @@ -18,5 +20,19 @@ func Init(ctx context.Context, logger *zap.Logger) (*raftstore.Store[repos.Repos if err != nil { return nil, stacktrace.Propagate(err, "failed to get aux raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "aux_")), params, func() repos.Repository { return &repo{} }) + return raftstore.Init(ctx, logger.With(zap.String("service", "aux_")), params, &repo{}) +} + +func (r *repo) GetRepo() repos.Repository { return r } + +func (r *repo) GetSnapshot() ([]byte, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +} + +func (r *repo) RestoreFromSnapshot([]byte) error { + return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +} + +func (r *repo) Apply(_ context.Context, _ consensus.Proposal) (any, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") } diff --git a/pkg/raftstore/consensus/proposal.go b/pkg/raftstore/consensus/proposal.go index a5e837d82..a70ba5b03 100644 --- a/pkg/raftstore/consensus/proposal.go +++ b/pkg/raftstore/consensus/proposal.go @@ -7,6 +7,7 @@ import ( "time" "github.com/google/uuid" + "github.com/interuss/dss/pkg/timestamp" "github.com/interuss/stacktrace" ) @@ -31,7 +32,11 @@ type Proposal struct { } func (c *Consensus) newProposal(ctx context.Context, requestType string, payload any, readOnly bool) (Proposal, error) { - // TODO - Fetch timestamp from context + timestamp, err := timestamp.RequestTimestampFromContext(ctx) + if err != nil || timestamp.IsZero() { + return Proposal{}, stacktrace.Propagate(err, "failed to get timestamp from context") + } + value, err := json.Marshal(payload) if err != nil { return Proposal{}, stacktrace.Propagate(err, "failed to serialize proposal payload") @@ -40,7 +45,7 @@ func (c *Consensus) newProposal(ctx context.Context, requestType string, payload return Proposal{ ID: uuid.NewString(), NodeID: c.nodeID, - Timestamp: time.Now().UTC(), + Timestamp: timestamp, RequestType: requestType, Value: value, ReadOnly: readOnly, diff --git a/pkg/raftstore/store.go b/pkg/raftstore/store.go index 12bdb78bd..7f833b1a4 100644 --- a/pkg/raftstore/store.go +++ b/pkg/raftstore/store.go @@ -2,46 +2,108 @@ package raftstore import ( "context" + "strings" + "github.com/interuss/dss/pkg/logging" "github.com/interuss/dss/pkg/raftstore/consensus" raftparams "github.com/interuss/dss/pkg/raftstore/params" "github.com/interuss/dss/pkg/store" + "github.com/interuss/dss/pkg/timestamp" "github.com/interuss/stacktrace" "go.uber.org/zap" ) +type RaftRepo[R any] interface { + GetRepo() R + // Apply is called on every committed entry. The proposal must be applied atomically. + Apply(ctx context.Context, proposal consensus.Proposal) (any, error) + GetSnapshot() ([]byte, error) + RestoreFromSnapshot(data []byte) error +} + type Store[R any] struct { - newRepo func() R - consensus *consensus.Consensus + logger *zap.Logger + + name string + raftRepo RaftRepo[R] + cancel context.CancelFunc + + Consensus *consensus.Consensus } -func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.ConnectParameters, newRepo func() R) (*Store[R], error) { +func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.ConnectParameters, r RaftRepo[R]) (*Store[R], error) { + ctx, cancel := context.WithCancel(ctx) + + store := &Store[R]{ + raftRepo: r, + logger: logging.WithValuesFromContext(ctx, logger), + cancel: cancel, + } commitC := make(chan consensus.EntryCommit) - consensusInstance, err := consensus.NewConsensus(ctx, logger, params, func() ([]byte, error) { return nil, nil }, commitC) + go store.processCommits(ctx, commitC) + + consensusInstance, err := consensus.NewConsensus(ctx, logger, params, r.GetSnapshot, commitC) if err != nil { return nil, stacktrace.Propagate(err, "failed to initialize consensus") } - // TODO: start consumer goroutine reading from commitC - return &Store[R]{ - newRepo: newRepo, - consensus: consensusInstance, - }, nil + store.Consensus = consensusInstance + + return store, nil } // Transact proposes the entry to Raft and blocks until it is committed and applied. -func (s *Store[R]) Transact(_ context.Context, _ store.OperationRequest) (any, error) { - // TODO: implement - return nil, nil +func (s *Store[R]) Transact(ctx context.Context, op store.OperationRequest) (any, error) { + payload, err := op.MarshalBinary() + if err != nil { + return nil, stacktrace.Propagate(err, "failed to marshal op %q", op.OperationID()) + } + return s.Consensus.HandleClientRequest(ctx, op.OperationID(), payload, isReadOnly(op.OperationID())) +} + +func isReadOnly(operationID string) bool { + for _, prefix := range []string{"Get", "Query", "Search", "List"} { + if strings.HasPrefix(operationID, prefix) { + return true + } + } + return false } -// Interact returns a repository that can be used to query the store without proposing a Raft entry. func (s *Store[R]) Interact(_ context.Context) (R, error) { - return s.newRepo(), nil + return s.raftRepo.GetRepo(), nil } -// Close shuts down the consensus instance. +// Close shuts down the consensus instance and processCommits loop. func (s *Store[R]) Close() error { - // TODO: implement + s.Consensus.Stop(context.Background()) + s.cancel() return nil } + +// processCommits reads committed entries from the consensus layer and applies them via Apply. +func (s *Store[R]) processCommits(ctx context.Context, commitCh <-chan consensus.EntryCommit) { + for { + select { + case <-ctx.Done(): + s.logger.Info("stopping commit processing loop") + return + case commit, ok := <-commitCh: + if !ok { + s.logger.Info("commit channel closed, stopping commit processing loop") + return + } + + if commit.SnapshotData != nil { + if err := s.raftRepo.RestoreFromSnapshot(commit.SnapshotData); err != nil { + s.logger.Error("failed to restore from snapshot", zap.Error(err)) + } + continue + } + + ctx = timestamp.WithRequestTimestamp(ctx, commit.Prop.Timestamp) + result, err := s.raftRepo.Apply(ctx, commit.Prop) + commit.Done <- consensus.ProposalResult{Result: result, Error: err} + } + } +} diff --git a/pkg/rid/store/raftstore/store.go b/pkg/rid/store/raftstore/store.go index 578aabeac..a52b6600c 100644 --- a/pkg/rid/store/raftstore/store.go +++ b/pkg/rid/store/raftstore/store.go @@ -3,7 +3,9 @@ package raftstore import ( "context" + dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/raftstore" + "github.com/interuss/dss/pkg/raftstore/consensus" "github.com/interuss/dss/pkg/rid/repos" ridraftparams "github.com/interuss/dss/pkg/rid/store/raftstore/params" "github.com/interuss/stacktrace" @@ -18,5 +20,19 @@ func Init(ctx context.Context, logger *zap.Logger) (*raftstore.Store[repos.Repos if err != nil { return nil, stacktrace.Propagate(err, "failed to get rid raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "rid")), params, func() repos.Repository { return &repo{} }) + return raftstore.Init(ctx, logger.With(zap.String("service", "rid")), params, &repo{}) +} + +func (r *repo) GetRepo() repos.Repository { return r } + +func (r *repo) GetSnapshot() ([]byte, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +} + +func (r *repo) RestoreFromSnapshot([]byte) error { + return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +} + +func (r *repo) Apply(_ context.Context, _ consensus.Proposal) (any, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") } diff --git a/pkg/scd/store/raftstore/store.go b/pkg/scd/store/raftstore/store.go index a7eeb663a..051670e35 100644 --- a/pkg/scd/store/raftstore/store.go +++ b/pkg/scd/store/raftstore/store.go @@ -3,7 +3,9 @@ package raftstore import ( "context" + dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/raftstore" + "github.com/interuss/dss/pkg/raftstore/consensus" "github.com/interuss/dss/pkg/scd/repos" scdraftparams "github.com/interuss/dss/pkg/scd/store/raftstore/params" "github.com/interuss/stacktrace" @@ -18,5 +20,19 @@ func Init(ctx context.Context, logger *zap.Logger) (*raftstore.Store[repos.Repos if err != nil { return nil, stacktrace.Propagate(err, "failed to get scd raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "scd")), params, func() repos.Repository { return &repo{} }) + return raftstore.Init(ctx, logger.With(zap.String("service", "scd")), params, &repo{}) +} + +func (r *repo) GetRepo() repos.Repository { return r } + +func (r *repo) GetSnapshot() ([]byte, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +} + +func (r *repo) RestoreFromSnapshot([]byte) error { + return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +} + +func (r *repo) Apply(_ context.Context, _ consensus.Proposal) (any, error) { + return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") } From f8218240a403fe737376c80d8d65aaadea50c1cf Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Tue, 30 Jun 2026 13:55:53 +0200 Subject: [PATCH 5/7] [raft/store] Add generated requesttype and isreadonly --- .../api/dummyoauth/interface.gen.go | 7 + .../example/api/rid/interface.gen.go | 80 ++++++++++ .../example/api/scd/interface.gen.go | 146 ++++++++++++++++++ interfaces/openapi-to-go-server/rendering.py | 13 ++ pkg/api/auxv1/interface.gen.go | 42 +++++ pkg/api/ridv1/interface.gen.go | 60 +++++++ pkg/api/ridv2/interface.gen.go | 60 +++++++ pkg/api/scdv1/interface.gen.go | 110 +++++++++++++ 8 files changed, 518 insertions(+) diff --git a/cmds/dummy-oauth/api/dummyoauth/interface.gen.go b/cmds/dummy-oauth/api/dummyoauth/interface.gen.go index 1439cfcca..3ce7e8237 100644 --- a/cmds/dummy-oauth/api/dummyoauth/interface.gen.go +++ b/cmds/dummy-oauth/api/dummyoauth/interface.gen.go @@ -29,6 +29,13 @@ type GetTokenRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetTokenRequestType = "GetToken" + +func (req *GetTokenRequest) RequestType() string { return GetTokenRequestType } + +func (req *GetTokenRequest) IsReadOnly() bool { return false } + type GetTokenResponseSet struct { // The requested token was generated successfully Response200 *TokenResponse diff --git a/interfaces/openapi-to-go-server/example/api/rid/interface.gen.go b/interfaces/openapi-to-go-server/example/api/rid/interface.gen.go index 25b39c03a..0cb1ab2b5 100644 --- a/interfaces/openapi-to-go-server/example/api/rid/interface.gen.go +++ b/interfaces/openapi-to-go-server/example/api/rid/interface.gen.go @@ -77,6 +77,15 @@ type SearchIdentificationServiceAreasRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SearchIdentificationServiceAreasRequestType = "SearchIdentificationServiceAreas" + +func (req *SearchIdentificationServiceAreasRequest) RequestType() string { + return SearchIdentificationServiceAreasRequestType +} + +func (req *SearchIdentificationServiceAreasRequest) IsReadOnly() bool { return true } + type SearchIdentificationServiceAreasResponseSet struct { // Identification Service Areas were successfully retrieved. Response200 *SearchIdentificationServiceAreasResponse @@ -104,6 +113,15 @@ type GetIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetIdentificationServiceAreaRequestType = "GetIdentificationServiceArea" + +func (req *GetIdentificationServiceAreaRequest) RequestType() string { + return GetIdentificationServiceAreaRequestType +} + +func (req *GetIdentificationServiceAreaRequest) IsReadOnly() bool { return true } + type GetIdentificationServiceAreaResponseSet struct { // Full information of the Identification Service Area was retrieved successfully. Response200 *GetIdentificationServiceAreaResponse @@ -137,6 +155,15 @@ type CreateIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateIdentificationServiceAreaRequestType = "CreateIdentificationServiceArea" + +func (req *CreateIdentificationServiceAreaRequest) RequestType() string { + return CreateIdentificationServiceAreaRequestType +} + +func (req *CreateIdentificationServiceAreaRequest) IsReadOnly() bool { return false } + type CreateIdentificationServiceAreaResponseSet struct { // An existing Identification Service Area was created successfully in the DSS. Response200 *PutIdentificationServiceAreaResponse @@ -178,6 +205,15 @@ type UpdateIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateIdentificationServiceAreaRequestType = "UpdateIdentificationServiceArea" + +func (req *UpdateIdentificationServiceAreaRequest) RequestType() string { + return UpdateIdentificationServiceAreaRequestType +} + +func (req *UpdateIdentificationServiceAreaRequest) IsReadOnly() bool { return false } + type UpdateIdentificationServiceAreaResponseSet struct { // An existing Identification Service Area was updated successfully in the DSS. Response200 *PutIdentificationServiceAreaResponse @@ -213,6 +249,15 @@ type DeleteIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteIdentificationServiceAreaRequestType = "DeleteIdentificationServiceArea" + +func (req *DeleteIdentificationServiceAreaRequest) RequestType() string { + return DeleteIdentificationServiceAreaRequestType +} + +func (req *DeleteIdentificationServiceAreaRequest) IsReadOnly() bool { return false } + type DeleteIdentificationServiceAreaResponseSet struct { // Identification Service Area was successfully deleted from DSS. Response200 *DeleteIdentificationServiceAreaResponse @@ -245,6 +290,13 @@ type SearchSubscriptionsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SearchSubscriptionsRequestType = "SearchSubscriptions" + +func (req *SearchSubscriptionsRequest) RequestType() string { return SearchSubscriptionsRequestType } + +func (req *SearchSubscriptionsRequest) IsReadOnly() bool { return true } + type SearchSubscriptionsResponseSet struct { // Subscriptions were retrieved successfully. Response200 *SearchSubscriptionsResponse @@ -272,6 +324,13 @@ type GetSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetSubscriptionRequestType = "GetSubscription" + +func (req *GetSubscriptionRequest) RequestType() string { return GetSubscriptionRequestType } + +func (req *GetSubscriptionRequest) IsReadOnly() bool { return true } + type GetSubscriptionResponseSet struct { // Subscription information was retrieved successfully. Response200 *GetSubscriptionResponse @@ -305,6 +364,13 @@ type CreateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateSubscriptionRequestType = "CreateSubscription" + +func (req *CreateSubscriptionRequest) RequestType() string { return CreateSubscriptionRequestType } + +func (req *CreateSubscriptionRequest) IsReadOnly() bool { return false } + type CreateSubscriptionResponseSet struct { // A new Subscription was created successfully. Response200 *PutSubscriptionResponse @@ -347,6 +413,13 @@ type UpdateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateSubscriptionRequestType = "UpdateSubscription" + +func (req *UpdateSubscriptionRequest) RequestType() string { return UpdateSubscriptionRequestType } + +func (req *UpdateSubscriptionRequest) IsReadOnly() bool { return false } + type UpdateSubscriptionResponseSet struct { // An existing Subscription was updated successfully. Response200 *PutSubscriptionResponse @@ -383,6 +456,13 @@ type DeleteSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteSubscriptionRequestType = "DeleteSubscription" + +func (req *DeleteSubscriptionRequest) RequestType() string { return DeleteSubscriptionRequestType } + +func (req *DeleteSubscriptionRequest) IsReadOnly() bool { return false } + type DeleteSubscriptionResponseSet struct { // Subscription was deleted successfully. Response200 *DeleteSubscriptionResponse diff --git a/interfaces/openapi-to-go-server/example/api/scd/interface.gen.go b/interfaces/openapi-to-go-server/example/api/scd/interface.gen.go index 75cdce626..cd4db85c0 100644 --- a/interfaces/openapi-to-go-server/example/api/scd/interface.gen.go +++ b/interfaces/openapi-to-go-server/example/api/scd/interface.gen.go @@ -174,6 +174,15 @@ type QueryOperationalIntentReferencesRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const QueryOperationalIntentReferencesRequestType = "QueryOperationalIntentReferences" + +func (req *QueryOperationalIntentReferencesRequest) RequestType() string { + return QueryOperationalIntentReferencesRequestType +} + +func (req *QueryOperationalIntentReferencesRequest) IsReadOnly() bool { return true } + type QueryOperationalIntentReferencesResponseSet struct { // Operational intents were successfully retrieved. Response200 *QueryOperationalIntentReferenceResponse @@ -204,6 +213,15 @@ type GetOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetOperationalIntentReferenceRequestType = "GetOperationalIntentReference" + +func (req *GetOperationalIntentReferenceRequest) RequestType() string { + return GetOperationalIntentReferenceRequestType +} + +func (req *GetOperationalIntentReferenceRequest) IsReadOnly() bool { return true } + type GetOperationalIntentReferenceResponseSet struct { // Operational intent reference was retrieved successfully. Response200 *GetOperationalIntentReferenceResponse @@ -240,6 +258,15 @@ type CreateOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateOperationalIntentReferenceRequestType = "CreateOperationalIntentReference" + +func (req *CreateOperationalIntentReferenceRequest) RequestType() string { + return CreateOperationalIntentReferenceRequestType +} + +func (req *CreateOperationalIntentReferenceRequest) IsReadOnly() bool { return false } + type CreateOperationalIntentReferenceResponseSet struct { // An operational intent reference was created successfully in the DSS. Response201 *ChangeOperationalIntentReferenceResponse @@ -287,6 +314,15 @@ type UpdateOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateOperationalIntentReferenceRequestType = "UpdateOperationalIntentReference" + +func (req *UpdateOperationalIntentReferenceRequest) RequestType() string { + return UpdateOperationalIntentReferenceRequestType +} + +func (req *UpdateOperationalIntentReferenceRequest) IsReadOnly() bool { return false } + type UpdateOperationalIntentReferenceResponseSet struct { // An operational intent reference was updated successfully in the DSS. Response200 *ChangeOperationalIntentReferenceResponse @@ -329,6 +365,15 @@ type DeleteOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteOperationalIntentReferenceRequestType = "DeleteOperationalIntentReference" + +func (req *DeleteOperationalIntentReferenceRequest) RequestType() string { + return DeleteOperationalIntentReferenceRequestType +} + +func (req *DeleteOperationalIntentReferenceRequest) IsReadOnly() bool { return false } + type DeleteOperationalIntentReferenceResponseSet struct { // The specified operational intent was successfully removed from the DSS. Response200 *ChangeOperationalIntentReferenceResponse @@ -370,6 +415,15 @@ type QueryConstraintReferencesRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const QueryConstraintReferencesRequestType = "QueryConstraintReferences" + +func (req *QueryConstraintReferencesRequest) RequestType() string { + return QueryConstraintReferencesRequestType +} + +func (req *QueryConstraintReferencesRequest) IsReadOnly() bool { return true } + type QueryConstraintReferencesResponseSet struct { // Constraint references were successfully retrieved. Response200 *QueryConstraintReferencesResponse @@ -400,6 +454,15 @@ type GetConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetConstraintReferenceRequestType = "GetConstraintReference" + +func (req *GetConstraintReferenceRequest) RequestType() string { + return GetConstraintReferenceRequestType +} + +func (req *GetConstraintReferenceRequest) IsReadOnly() bool { return true } + type GetConstraintReferenceResponseSet struct { // Constraint reference was retrieved successfully. Response200 *GetConstraintReferenceResponse @@ -436,6 +499,15 @@ type CreateConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateConstraintReferenceRequestType = "CreateConstraintReference" + +func (req *CreateConstraintReferenceRequest) RequestType() string { + return CreateConstraintReferenceRequestType +} + +func (req *CreateConstraintReferenceRequest) IsReadOnly() bool { return false } + type CreateConstraintReferenceResponseSet struct { // A constraint reference was created successfully in the DSS. Response201 *ChangeConstraintReferenceResponse @@ -480,6 +552,15 @@ type UpdateConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateConstraintReferenceRequestType = "UpdateConstraintReference" + +func (req *UpdateConstraintReferenceRequest) RequestType() string { + return UpdateConstraintReferenceRequestType +} + +func (req *UpdateConstraintReferenceRequest) IsReadOnly() bool { return false } + type UpdateConstraintReferenceResponseSet struct { // A constraint reference was updated successfully in the DSS. Response200 *ChangeConstraintReferenceResponse @@ -518,6 +599,15 @@ type DeleteConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteConstraintReferenceRequestType = "DeleteConstraintReference" + +func (req *DeleteConstraintReferenceRequest) RequestType() string { + return DeleteConstraintReferenceRequestType +} + +func (req *DeleteConstraintReferenceRequest) IsReadOnly() bool { return false } + type DeleteConstraintReferenceResponseSet struct { // The constraint was successfully removed from the DSS. Response200 *ChangeConstraintReferenceResponse @@ -556,6 +646,13 @@ type QuerySubscriptionsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const QuerySubscriptionsRequestType = "QuerySubscriptions" + +func (req *QuerySubscriptionsRequest) RequestType() string { return QuerySubscriptionsRequestType } + +func (req *QuerySubscriptionsRequest) IsReadOnly() bool { return true } + type QuerySubscriptionsResponseSet struct { // Subscriptions were retrieved successfully. Response200 *QuerySubscriptionsResponse @@ -586,6 +683,13 @@ type GetSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetSubscriptionRequestType = "GetSubscription" + +func (req *GetSubscriptionRequest) RequestType() string { return GetSubscriptionRequestType } + +func (req *GetSubscriptionRequest) IsReadOnly() bool { return true } + type GetSubscriptionResponseSet struct { // Subscription information was retrieved successfully. Response200 *GetSubscriptionResponse @@ -622,6 +726,13 @@ type CreateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateSubscriptionRequestType = "CreateSubscription" + +func (req *CreateSubscriptionRequest) RequestType() string { return CreateSubscriptionRequestType } + +func (req *CreateSubscriptionRequest) IsReadOnly() bool { return false } + type CreateSubscriptionResponseSet struct { // A new subscription was created successfully. Response200 *PutSubscriptionResponse @@ -664,6 +775,13 @@ type UpdateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateSubscriptionRequestType = "UpdateSubscription" + +func (req *UpdateSubscriptionRequest) RequestType() string { return UpdateSubscriptionRequestType } + +func (req *UpdateSubscriptionRequest) IsReadOnly() bool { return false } + type UpdateSubscriptionResponseSet struct { // A subscription was updated successfully. Response200 *PutSubscriptionResponse @@ -701,6 +819,13 @@ type DeleteSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteSubscriptionRequestType = "DeleteSubscription" + +func (req *DeleteSubscriptionRequest) RequestType() string { return DeleteSubscriptionRequestType } + +func (req *DeleteSubscriptionRequest) IsReadOnly() bool { return false } + type DeleteSubscriptionResponseSet struct { // Subscription was successfully removed from DSS. Response200 *DeleteSubscriptionResponse @@ -739,6 +864,13 @@ type MakeDssReportRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const MakeDssReportRequestType = "MakeDssReport" + +func (req *MakeDssReportRequest) RequestType() string { return MakeDssReportRequestType } + +func (req *MakeDssReportRequest) IsReadOnly() bool { return false } + type MakeDssReportResponseSet struct { // A new Report was created successfully (and archived). Response201 *ErrorReport @@ -767,6 +899,13 @@ type GetUssAvailabilityRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetUssAvailabilityRequestType = "GetUssAvailability" + +func (req *GetUssAvailabilityRequest) RequestType() string { return GetUssAvailabilityRequestType } + +func (req *GetUssAvailabilityRequest) IsReadOnly() bool { return true } + type GetUssAvailabilityResponseSet struct { // Availability status of specified USS was successfully retrieved. Response200 *UssAvailabilityStatusResponse @@ -800,6 +939,13 @@ type SetUssAvailabilityRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SetUssAvailabilityRequestType = "SetUssAvailability" + +func (req *SetUssAvailabilityRequest) RequestType() string { return SetUssAvailabilityRequestType } + +func (req *SetUssAvailabilityRequest) IsReadOnly() bool { return false } + type SetUssAvailabilityResponseSet struct { // Availability status of specified USS was successfully updated. Response200 *UssAvailabilityStatusResponse diff --git a/interfaces/openapi-to-go-server/rendering.py b/interfaces/openapi-to-go-server/rendering.py index 3d3b25cd0..ce2fd2d3d 100644 --- a/interfaces/openapi-to-go-server/rendering.py +++ b/interfaces/openapi-to-go-server/rendering.py @@ -202,6 +202,19 @@ def implementation_interface( lines.extend(indent(body, 1)) lines.append("}") + lines.append("") + + lines.append( + 'const {}OperationID = "{}"'.format( + operation.interface_name, operation.interface_name + ) + ) + lines.append("") + lines.append( + "func (req *{}) OperationID() string {{ return {}OperationID }}".format( + operation.request_type_name, operation.interface_name + ) + ) # Declare response type for operation lines.append("type {} struct {{".format(operation.response_type_name)) diff --git a/pkg/api/auxv1/interface.gen.go b/pkg/api/auxv1/interface.gen.go index a7a006c2b..9cea095a3 100644 --- a/pkg/api/auxv1/interface.gen.go +++ b/pkg/api/auxv1/interface.gen.go @@ -48,6 +48,11 @@ type GetVersionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetVersionOperationID = "GetVersion" + +func (req *GetVersionRequest) OperationID() string { return GetVersionOperationID } + type GetVersionResponseSet struct { // The version of the DSS is successfully returned. Response200 *VersionResponse @@ -63,6 +68,11 @@ type ValidateOauthRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const ValidateOauthOperationID = "ValidateOauth" + +func (req *ValidateOauthRequest) OperationID() string { return ValidateOauthOperationID } + type ValidateOauthResponseSet struct { // The provided token was validated. Response200 *api.EmptyResponseBody @@ -81,6 +91,11 @@ type GetPoolRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetPoolOperationID = "GetPool" + +func (req *GetPoolRequest) OperationID() string { return GetPoolOperationID } + type GetPoolResponseSet struct { // The information is successfully returned. Response200 *PoolResponse @@ -102,6 +117,11 @@ type GetDSSInstancesRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetDSSInstancesOperationID = "GetDSSInstances" + +func (req *GetDSSInstancesRequest) OperationID() string { return GetDSSInstancesOperationID } + type GetDSSInstancesResponseSet struct { // The known DSS instances participating in the pool are successfully returned. Response200 *DSSInstancesResponse @@ -132,6 +152,13 @@ type PutDSSInstancesHeartbeatRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const PutDSSInstancesHeartbeatOperationID = "PutDSSInstancesHeartbeat" + +func (req *PutDSSInstancesHeartbeatRequest) OperationID() string { + return PutDSSInstancesHeartbeatOperationID +} + type PutDSSInstancesHeartbeatResponseSet struct { // The heartbeat have been recorded. The known DSS instances participating in the pool are successfully returned. Response201 *DSSInstancesResponse @@ -156,6 +183,11 @@ type GetAcceptedCAsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetAcceptedCAsOperationID = "GetAcceptedCAs" + +func (req *GetAcceptedCAsRequest) OperationID() string { return GetAcceptedCAsOperationID } + type GetAcceptedCAsResponseSet struct { // The information is successfully returned. Response200 *CAsResponse @@ -171,6 +203,11 @@ type GetInstanceCAsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetInstanceCAsOperationID = "GetInstanceCAs" + +func (req *GetInstanceCAsRequest) OperationID() string { return GetInstanceCAsOperationID } + type GetInstanceCAsResponseSet struct { // The information is successfully returned. Response200 *CAsResponse @@ -186,6 +223,11 @@ type GetGlobalOptionsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetGlobalOptionsOperationID = "GetGlobalOptions" + +func (req *GetGlobalOptionsRequest) OperationID() string { return GetGlobalOptionsOperationID } + type GetGlobalOptionsResponseSet struct { // The information is successfully returned. Response200 *GlobalOptionsResponse diff --git a/pkg/api/ridv1/interface.gen.go b/pkg/api/ridv1/interface.gen.go index e0807ffb6..ef1910a2e 100644 --- a/pkg/api/ridv1/interface.gen.go +++ b/pkg/api/ridv1/interface.gen.go @@ -77,6 +77,13 @@ type SearchIdentificationServiceAreasRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SearchIdentificationServiceAreasOperationID = "SearchIdentificationServiceAreas" + +func (req *SearchIdentificationServiceAreasRequest) OperationID() string { + return SearchIdentificationServiceAreasOperationID +} + type SearchIdentificationServiceAreasResponseSet struct { // Identification Service Areas were successfully retrieved. Response200 *SearchIdentificationServiceAreasResponse @@ -104,6 +111,13 @@ type GetIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetIdentificationServiceAreaOperationID = "GetIdentificationServiceArea" + +func (req *GetIdentificationServiceAreaRequest) OperationID() string { + return GetIdentificationServiceAreaOperationID +} + type GetIdentificationServiceAreaResponseSet struct { // Full information of the Identification Service Area was retrieved successfully. Response200 *GetIdentificationServiceAreaResponse @@ -137,6 +151,13 @@ type CreateIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateIdentificationServiceAreaOperationID = "CreateIdentificationServiceArea" + +func (req *CreateIdentificationServiceAreaRequest) OperationID() string { + return CreateIdentificationServiceAreaOperationID +} + type CreateIdentificationServiceAreaResponseSet struct { // An existing Identification Service Area was created successfully in the DSS. Response200 *PutIdentificationServiceAreaResponse @@ -178,6 +199,13 @@ type UpdateIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateIdentificationServiceAreaOperationID = "UpdateIdentificationServiceArea" + +func (req *UpdateIdentificationServiceAreaRequest) OperationID() string { + return UpdateIdentificationServiceAreaOperationID +} + type UpdateIdentificationServiceAreaResponseSet struct { // An existing Identification Service Area was updated successfully in the DSS. Response200 *PutIdentificationServiceAreaResponse @@ -213,6 +241,13 @@ type DeleteIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteIdentificationServiceAreaOperationID = "DeleteIdentificationServiceArea" + +func (req *DeleteIdentificationServiceAreaRequest) OperationID() string { + return DeleteIdentificationServiceAreaOperationID +} + type DeleteIdentificationServiceAreaResponseSet struct { // Identification Service Area was successfully deleted from DSS. Response200 *DeleteIdentificationServiceAreaResponse @@ -245,6 +280,11 @@ type SearchSubscriptionsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SearchSubscriptionsOperationID = "SearchSubscriptions" + +func (req *SearchSubscriptionsRequest) OperationID() string { return SearchSubscriptionsOperationID } + type SearchSubscriptionsResponseSet struct { // Subscriptions were retrieved successfully. Response200 *SearchSubscriptionsResponse @@ -272,6 +312,11 @@ type GetSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetSubscriptionOperationID = "GetSubscription" + +func (req *GetSubscriptionRequest) OperationID() string { return GetSubscriptionOperationID } + type GetSubscriptionResponseSet struct { // Subscription information was retrieved successfully. Response200 *GetSubscriptionResponse @@ -305,6 +350,11 @@ type CreateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateSubscriptionOperationID = "CreateSubscription" + +func (req *CreateSubscriptionRequest) OperationID() string { return CreateSubscriptionOperationID } + type CreateSubscriptionResponseSet struct { // A new Subscription was created successfully. Response200 *PutSubscriptionResponse @@ -347,6 +397,11 @@ type UpdateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateSubscriptionOperationID = "UpdateSubscription" + +func (req *UpdateSubscriptionRequest) OperationID() string { return UpdateSubscriptionOperationID } + type UpdateSubscriptionResponseSet struct { // An existing Subscription was updated successfully. Response200 *PutSubscriptionResponse @@ -383,6 +438,11 @@ type DeleteSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteSubscriptionOperationID = "DeleteSubscription" + +func (req *DeleteSubscriptionRequest) OperationID() string { return DeleteSubscriptionOperationID } + type DeleteSubscriptionResponseSet struct { // Subscription was deleted successfully. Response200 *DeleteSubscriptionResponse diff --git a/pkg/api/ridv2/interface.gen.go b/pkg/api/ridv2/interface.gen.go index 406d8460f..4c39b5d7a 100644 --- a/pkg/api/ridv2/interface.gen.go +++ b/pkg/api/ridv2/interface.gen.go @@ -80,6 +80,13 @@ type SearchIdentificationServiceAreasRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SearchIdentificationServiceAreasOperationID = "SearchIdentificationServiceAreas" + +func (req *SearchIdentificationServiceAreasRequest) OperationID() string { + return SearchIdentificationServiceAreasOperationID +} + type SearchIdentificationServiceAreasResponseSet struct { // Identification Service Areas were successfully retrieved. Response200 *SearchIdentificationServiceAreasResponse @@ -107,6 +114,13 @@ type GetIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetIdentificationServiceAreaOperationID = "GetIdentificationServiceArea" + +func (req *GetIdentificationServiceAreaRequest) OperationID() string { + return GetIdentificationServiceAreaOperationID +} + type GetIdentificationServiceAreaResponseSet struct { // Full information of the Identification Service Area was retrieved successfully. Response200 *GetIdentificationServiceAreaResponse @@ -140,6 +154,13 @@ type CreateIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateIdentificationServiceAreaOperationID = "CreateIdentificationServiceArea" + +func (req *CreateIdentificationServiceAreaRequest) OperationID() string { + return CreateIdentificationServiceAreaOperationID +} + type CreateIdentificationServiceAreaResponseSet struct { // An existing Identification Service Area was created successfully in the DSS. Response200 *PutIdentificationServiceAreaResponse @@ -181,6 +202,13 @@ type UpdateIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateIdentificationServiceAreaOperationID = "UpdateIdentificationServiceArea" + +func (req *UpdateIdentificationServiceAreaRequest) OperationID() string { + return UpdateIdentificationServiceAreaOperationID +} + type UpdateIdentificationServiceAreaResponseSet struct { // An existing Identification Service Area was updated successfully in the DSS. Response200 *PutIdentificationServiceAreaResponse @@ -216,6 +244,13 @@ type DeleteIdentificationServiceAreaRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteIdentificationServiceAreaOperationID = "DeleteIdentificationServiceArea" + +func (req *DeleteIdentificationServiceAreaRequest) OperationID() string { + return DeleteIdentificationServiceAreaOperationID +} + type DeleteIdentificationServiceAreaResponseSet struct { // Identification Service Area was successfully deleted from DSS. Response200 *DeleteIdentificationServiceAreaResponse @@ -248,6 +283,11 @@ type SearchSubscriptionsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SearchSubscriptionsOperationID = "SearchSubscriptions" + +func (req *SearchSubscriptionsRequest) OperationID() string { return SearchSubscriptionsOperationID } + type SearchSubscriptionsResponseSet struct { // Subscriptions were retrieved successfully. Response200 *SearchSubscriptionsResponse @@ -275,6 +315,11 @@ type GetSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetSubscriptionOperationID = "GetSubscription" + +func (req *GetSubscriptionRequest) OperationID() string { return GetSubscriptionOperationID } + type GetSubscriptionResponseSet struct { // Subscription information was retrieved successfully. Response200 *GetSubscriptionResponse @@ -308,6 +353,11 @@ type CreateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateSubscriptionOperationID = "CreateSubscription" + +func (req *CreateSubscriptionRequest) OperationID() string { return CreateSubscriptionOperationID } + type CreateSubscriptionResponseSet struct { // A new Subscription was created successfully. Response200 *PutSubscriptionResponse @@ -349,6 +399,11 @@ type UpdateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateSubscriptionOperationID = "UpdateSubscription" + +func (req *UpdateSubscriptionRequest) OperationID() string { return UpdateSubscriptionOperationID } + type UpdateSubscriptionResponseSet struct { // An existing Subscription was updated successfully. Response200 *PutSubscriptionResponse @@ -384,6 +439,11 @@ type DeleteSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteSubscriptionOperationID = "DeleteSubscription" + +func (req *DeleteSubscriptionRequest) OperationID() string { return DeleteSubscriptionOperationID } + type DeleteSubscriptionResponseSet struct { // Subscription was deleted successfully. Response200 *DeleteSubscriptionResponse diff --git a/pkg/api/scdv1/interface.gen.go b/pkg/api/scdv1/interface.gen.go index cff1dd64f..46089d2df 100644 --- a/pkg/api/scdv1/interface.gen.go +++ b/pkg/api/scdv1/interface.gen.go @@ -174,6 +174,13 @@ type QueryOperationalIntentReferencesRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const QueryOperationalIntentReferencesOperationID = "QueryOperationalIntentReferences" + +func (req *QueryOperationalIntentReferencesRequest) OperationID() string { + return QueryOperationalIntentReferencesOperationID +} + type QueryOperationalIntentReferencesResponseSet struct { // Operational intents were successfully retrieved. Response200 *QueryOperationalIntentReferenceResponse @@ -204,6 +211,13 @@ type GetOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetOperationalIntentReferenceOperationID = "GetOperationalIntentReference" + +func (req *GetOperationalIntentReferenceRequest) OperationID() string { + return GetOperationalIntentReferenceOperationID +} + type GetOperationalIntentReferenceResponseSet struct { // Operational intent reference was retrieved successfully. Response200 *GetOperationalIntentReferenceResponse @@ -240,6 +254,13 @@ type CreateOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateOperationalIntentReferenceOperationID = "CreateOperationalIntentReference" + +func (req *CreateOperationalIntentReferenceRequest) OperationID() string { + return CreateOperationalIntentReferenceOperationID +} + type CreateOperationalIntentReferenceResponseSet struct { // An operational intent reference was created successfully in the DSS. Response201 *ChangeOperationalIntentReferenceResponse @@ -287,6 +308,13 @@ type UpdateOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateOperationalIntentReferenceOperationID = "UpdateOperationalIntentReference" + +func (req *UpdateOperationalIntentReferenceRequest) OperationID() string { + return UpdateOperationalIntentReferenceOperationID +} + type UpdateOperationalIntentReferenceResponseSet struct { // An operational intent reference was updated successfully in the DSS. Response200 *ChangeOperationalIntentReferenceResponse @@ -329,6 +357,13 @@ type DeleteOperationalIntentReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteOperationalIntentReferenceOperationID = "DeleteOperationalIntentReference" + +func (req *DeleteOperationalIntentReferenceRequest) OperationID() string { + return DeleteOperationalIntentReferenceOperationID +} + type DeleteOperationalIntentReferenceResponseSet struct { // The specified operational intent was successfully removed from the DSS. Response200 *ChangeOperationalIntentReferenceResponse @@ -370,6 +405,13 @@ type QueryConstraintReferencesRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const QueryConstraintReferencesOperationID = "QueryConstraintReferences" + +func (req *QueryConstraintReferencesRequest) OperationID() string { + return QueryConstraintReferencesOperationID +} + type QueryConstraintReferencesResponseSet struct { // Constraint references were successfully retrieved. Response200 *QueryConstraintReferencesResponse @@ -400,6 +442,13 @@ type GetConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetConstraintReferenceOperationID = "GetConstraintReference" + +func (req *GetConstraintReferenceRequest) OperationID() string { + return GetConstraintReferenceOperationID +} + type GetConstraintReferenceResponseSet struct { // Constraint reference was retrieved successfully. Response200 *GetConstraintReferenceResponse @@ -436,6 +485,13 @@ type CreateConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateConstraintReferenceOperationID = "CreateConstraintReference" + +func (req *CreateConstraintReferenceRequest) OperationID() string { + return CreateConstraintReferenceOperationID +} + type CreateConstraintReferenceResponseSet struct { // A constraint reference was created successfully in the DSS. Response201 *ChangeConstraintReferenceResponse @@ -480,6 +536,13 @@ type UpdateConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateConstraintReferenceOperationID = "UpdateConstraintReference" + +func (req *UpdateConstraintReferenceRequest) OperationID() string { + return UpdateConstraintReferenceOperationID +} + type UpdateConstraintReferenceResponseSet struct { // A constraint reference was updated successfully in the DSS. Response200 *ChangeConstraintReferenceResponse @@ -518,6 +581,13 @@ type DeleteConstraintReferenceRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteConstraintReferenceOperationID = "DeleteConstraintReference" + +func (req *DeleteConstraintReferenceRequest) OperationID() string { + return DeleteConstraintReferenceOperationID +} + type DeleteConstraintReferenceResponseSet struct { // The constraint was successfully removed from the DSS. Response200 *ChangeConstraintReferenceResponse @@ -556,6 +626,11 @@ type QuerySubscriptionsRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const QuerySubscriptionsOperationID = "QuerySubscriptions" + +func (req *QuerySubscriptionsRequest) OperationID() string { return QuerySubscriptionsOperationID } + type QuerySubscriptionsResponseSet struct { // Subscriptions were retrieved successfully. Response200 *QuerySubscriptionsResponse @@ -586,6 +661,11 @@ type GetSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetSubscriptionOperationID = "GetSubscription" + +func (req *GetSubscriptionRequest) OperationID() string { return GetSubscriptionOperationID } + type GetSubscriptionResponseSet struct { // Subscription information was retrieved successfully. Response200 *GetSubscriptionResponse @@ -622,6 +702,11 @@ type CreateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const CreateSubscriptionOperationID = "CreateSubscription" + +func (req *CreateSubscriptionRequest) OperationID() string { return CreateSubscriptionOperationID } + type CreateSubscriptionResponseSet struct { // A new subscription was created successfully. Response200 *PutSubscriptionResponse @@ -664,6 +749,11 @@ type UpdateSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const UpdateSubscriptionOperationID = "UpdateSubscription" + +func (req *UpdateSubscriptionRequest) OperationID() string { return UpdateSubscriptionOperationID } + type UpdateSubscriptionResponseSet struct { // A subscription was updated successfully. Response200 *PutSubscriptionResponse @@ -701,6 +791,11 @@ type DeleteSubscriptionRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const DeleteSubscriptionOperationID = "DeleteSubscription" + +func (req *DeleteSubscriptionRequest) OperationID() string { return DeleteSubscriptionOperationID } + type DeleteSubscriptionResponseSet struct { // Subscription was successfully removed from DSS. Response200 *DeleteSubscriptionResponse @@ -739,6 +834,11 @@ type MakeDssReportRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const MakeDssReportOperationID = "MakeDssReport" + +func (req *MakeDssReportRequest) OperationID() string { return MakeDssReportOperationID } + type MakeDssReportResponseSet struct { // A new Report was created successfully (and archived). Response201 *ErrorReport @@ -767,6 +867,11 @@ type GetUssAvailabilityRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const GetUssAvailabilityOperationID = "GetUssAvailability" + +func (req *GetUssAvailabilityRequest) OperationID() string { return GetUssAvailabilityOperationID } + type GetUssAvailabilityResponseSet struct { // Availability status of specified USS was successfully retrieved. Response200 *UssAvailabilityStatusResponse @@ -800,6 +905,11 @@ type SetUssAvailabilityRequest struct { // The result of attempting to authorize this request Auth api.AuthorizationResult } + +const SetUssAvailabilityOperationID = "SetUssAvailability" + +func (req *SetUssAvailabilityRequest) OperationID() string { return SetUssAvailabilityOperationID } + type SetUssAvailabilityResponseSet struct { // Availability status of specified USS was successfully updated. Response200 *UssAvailabilityStatusResponse From e14ebeb3535d4d68abd710462b8f7e091c2444bc Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Tue, 30 Jun 2026 14:09:03 +0200 Subject: [PATCH 6/7] [raft/aux] Pass down locality --- cmds/core-service/main.go | 10 +++++----- cmds/db-manager/cleanup/evict.go | 4 ++-- pkg/aux_/store/raftstore/store.go | 4 ++-- pkg/aux_/store/store.go | 4 ++-- pkg/locality/locality.go | 23 +++++++++++++++++++++++ pkg/raftstore/consensus/consensus.go | 17 +++++++++-------- pkg/raftstore/consensus/proposal.go | 2 ++ pkg/raftstore/store.go | 4 ++-- pkg/rid/store/raftstore/store.go | 4 ++-- pkg/rid/store/store.go | 4 ++-- pkg/scd/store/raftstore/store.go | 4 ++-- pkg/scd/store/store.go | 4 ++-- 12 files changed, 55 insertions(+), 29 deletions(-) create mode 100644 pkg/locality/locality.go diff --git a/cmds/core-service/main.go b/cmds/core-service/main.go index c4ffe2840..d31d1a98d 100644 --- a/cmds/core-service/main.go +++ b/cmds/core-service/main.go @@ -90,7 +90,7 @@ func createKeyResolver() (auth.KeyResolver, error) { } func createAuxServer(ctx context.Context, locality string, publicEndpoint string, opts params.Options, logger *zap.Logger) (*aux.Server, error) { - auxStore, err := auxs.Init(ctx, logger, true) + auxStore, err := auxs.Init(ctx, logger, true, locality) if err != nil { return nil, err } @@ -111,7 +111,7 @@ func createAuxServer(ctx context.Context, locality string, publicEndpoint string func createRIDServers(ctx context.Context, locality string, logger *zap.Logger) (*rid_v1.Server, *rid_v2.Server, error) { - ridStore, err := rids.Init(ctx, logger, true) + ridStore, err := rids.Init(ctx, logger, true, locality) if err != nil { return nil, nil, err } @@ -141,9 +141,9 @@ func createRIDServers(ctx context.Context, locality string, logger *zap.Logger) }, nil } -func createSCDServer(ctx context.Context, logger *zap.Logger) (*scd.Server, error) { +func createSCDServer(ctx context.Context, logger *zap.Logger, locality string) (*scd.Server, error) { - scdStore, err := scds.Init(ctx, logger, true) + scdStore, err := scds.Init(ctx, logger, true, locality) if err != nil { return nil, err } @@ -327,7 +327,7 @@ func RunHTTPServer(ctx context.Context, ctxCanceler func(), address, locality st // Initialize strategic conflict detection if *enableSCD { - scdV1Server, err = createSCDServer(ctx, logger) + scdV1Server, err = createSCDServer(ctx, logger, locality) if err != nil { return stacktrace.Propagate(err, "Failed to create strategic conflict detection server") } diff --git a/cmds/db-manager/cleanup/evict.go b/cmds/db-manager/cleanup/evict.go index efb927d53..8ca0f47b0 100644 --- a/cmds/db-manager/cleanup/evict.go +++ b/cmds/db-manager/cleanup/evict.go @@ -55,12 +55,12 @@ func evict(cmd *cobra.Command, _ []string) error { logger := logging.WithValuesFromContext(ctx, logging.Logger) - scdStore, err := scds.Init(ctx, logger, false) + scdStore, err := scds.Init(ctx, logger, false, *locality) if err != nil { return err } - ridStore, err := rids.Init(ctx, logger, false) + ridStore, err := rids.Init(ctx, logger, false, *locality) if err != nil { return err } diff --git a/pkg/aux_/store/raftstore/store.go b/pkg/aux_/store/raftstore/store.go index eb9be35de..e1db50062 100644 --- a/pkg/aux_/store/raftstore/store.go +++ b/pkg/aux_/store/raftstore/store.go @@ -15,12 +15,12 @@ import ( // repo is a full implementation of aux_.repos.Repository for Raft-based storage. type repo struct{} -func Init(ctx context.Context, logger *zap.Logger) (*raftstore.Store[repos.Repository], error) { +func Init(ctx context.Context, logger *zap.Logger, locality string) (*raftstore.Store[repos.Repository], error) { params, err := auxraftparams.GetConnectParameters() if err != nil { return nil, stacktrace.Propagate(err, "failed to get aux raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "aux_")), params, &repo{}) + return raftstore.Init(ctx, logger.With(zap.String("service", "aux_")), locality, params, &repo{}) } func (r *repo) GetRepo() repos.Repository { return r } diff --git a/pkg/aux_/store/store.go b/pkg/aux_/store/store.go index 2532842a7..93148ff80 100644 --- a/pkg/aux_/store/store.go +++ b/pkg/aux_/store/store.go @@ -19,12 +19,12 @@ import ( type Store = dssstore.Store[repos.Repository] // Init selects and initializes the aux store backend. -func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (Store, error) { +func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool, locality string) (Store, error) { switch storeType := params.GetStoreParameters().StoreType; storeType { case params.SQLStoreType: return auxsqlstore.Init(ctx, logger, withCheckCron) case params.RaftStoreType: - return auxraftstore.Init(ctx, logger) + return auxraftstore.Init(ctx, logger, locality) case params.MemStoreType: return auxmemstore.Init(ctx, logger) default: diff --git a/pkg/locality/locality.go b/pkg/locality/locality.go new file mode 100644 index 000000000..1e9969708 --- /dev/null +++ b/pkg/locality/locality.go @@ -0,0 +1,23 @@ +package locality + +import ( + "context" + + "github.com/interuss/stacktrace" +) + +type localityKey struct{} + +// LocalityFromContext returns the locality from the context, or an error if not present. +func LocalityFromContext(ctx context.Context) (string, error) { + locality, ok := ctx.Value(localityKey{}).(string) + if !ok { + return "", stacktrace.NewError("locality not found in context") + } + return locality, nil +} + +// WithLocality returns a new context with the given locality. +func WithLocality(ctx context.Context, locality string) context.Context { + return context.WithValue(ctx, localityKey{}, locality) +} diff --git a/pkg/raftstore/consensus/consensus.go b/pkg/raftstore/consensus/consensus.go index f41b36e14..5ede3e396 100644 --- a/pkg/raftstore/consensus/consensus.go +++ b/pkg/raftstore/consensus/consensus.go @@ -24,8 +24,9 @@ import ( type Consensus struct { logger *zap.Logger - nodeID uint64 - node raft.Node + locality string + nodeID uint64 + node raft.Node transport *rafthttp.Transport server *http.Server @@ -45,7 +46,7 @@ type Consensus struct { appliedIndex uint64 } -func NewConsensus(ctx context.Context, logger *zap.Logger, connectParams params.ConnectParameters, provider snapshotProvider, commitC chan<- EntryCommit) (*Consensus, error) { +func NewConsensus(ctx context.Context, logger *zap.Logger, locality string, connectParams params.ConnectParameters, provider snapshotProvider, commitC chan<- EntryCommit) (*Consensus, error) { storage, old, err := newStorage(ctx, logger.With(zap.String("component", "storage")), connectParams.DataDir, connectParams.NodeID, provider, connectParams.SnapshotCatchupEntries) if err != nil { return nil, stacktrace.Propagate(err, "failed to initialize storage") @@ -74,11 +75,11 @@ func NewConsensus(ctx context.Context, logger *zap.Logger, connectParams params. consensus := &Consensus{ logger: logging.WithValuesFromContext(ctx, logger), - nodeID: connectParams.NodeID, - node: node, - - storage: storage, - commitC: commitC, + nodeID: connectParams.NodeID, + node: node, + locality: locality, + storage: storage, + commitC: commitC, shutdownTimeout: 2 * connectParams.ElectionInterval(), serverErrC: make(chan error, 1), diff --git a/pkg/raftstore/consensus/proposal.go b/pkg/raftstore/consensus/proposal.go index a70ba5b03..efdc24fea 100644 --- a/pkg/raftstore/consensus/proposal.go +++ b/pkg/raftstore/consensus/proposal.go @@ -20,6 +20,7 @@ type EntryCommit struct { type Proposal struct { ID string `json:"id"` + Locality string `json:"locality"` NodeID uint64 `json:"node_id"` Timestamp time.Time `json:"timestamp"` RequestType string `json:"request_type"` @@ -44,6 +45,7 @@ func (c *Consensus) newProposal(ctx context.Context, requestType string, payload return Proposal{ ID: uuid.NewString(), + Locality: c.locality, NodeID: c.nodeID, Timestamp: timestamp, RequestType: requestType, diff --git a/pkg/raftstore/store.go b/pkg/raftstore/store.go index 7f833b1a4..a3f69d891 100644 --- a/pkg/raftstore/store.go +++ b/pkg/raftstore/store.go @@ -31,7 +31,7 @@ type Store[R any] struct { Consensus *consensus.Consensus } -func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.ConnectParameters, r RaftRepo[R]) (*Store[R], error) { +func Init[R any](ctx context.Context, logger *zap.Logger, locality string, params raftparams.ConnectParameters, r RaftRepo[R]) (*Store[R], error) { ctx, cancel := context.WithCancel(ctx) store := &Store[R]{ @@ -42,7 +42,7 @@ func Init[R any](ctx context.Context, logger *zap.Logger, params raftparams.Conn commitC := make(chan consensus.EntryCommit) go store.processCommits(ctx, commitC) - consensusInstance, err := consensus.NewConsensus(ctx, logger, params, r.GetSnapshot, commitC) + consensusInstance, err := consensus.NewConsensus(ctx, logger, locality, params, r.GetSnapshot, commitC) if err != nil { return nil, stacktrace.Propagate(err, "failed to initialize consensus") } diff --git a/pkg/rid/store/raftstore/store.go b/pkg/rid/store/raftstore/store.go index a52b6600c..0cc69cbb2 100644 --- a/pkg/rid/store/raftstore/store.go +++ b/pkg/rid/store/raftstore/store.go @@ -15,12 +15,12 @@ import ( // repo is a full implementation of rid.repos.Repository for Raft-based storage. type repo struct{} -func Init(ctx context.Context, logger *zap.Logger) (*raftstore.Store[repos.Repository], error) { +func Init(ctx context.Context, logger *zap.Logger, locality string) (*raftstore.Store[repos.Repository], error) { params, err := ridraftparams.GetConnectParameters() if err != nil { return nil, stacktrace.Propagate(err, "failed to get rid raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "rid")), params, &repo{}) + return raftstore.Init(ctx, logger.With(zap.String("service", "rid")), locality, params, &repo{}) } func (r *repo) GetRepo() repos.Repository { return r } diff --git a/pkg/rid/store/store.go b/pkg/rid/store/store.go index 7f2dc6c20..d9a1235c2 100644 --- a/pkg/rid/store/store.go +++ b/pkg/rid/store/store.go @@ -18,13 +18,13 @@ import ( type Store = dssstore.Store[repos.Repository] // Init selects and initializes the rid store backend. -func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (Store, error) { +func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool, locality string) (Store, error) { storeType := params.GetStoreParameters().StoreType switch storeType { case params.SQLStoreType: return ridsqlstore.Init(ctx, logger, withCheckCron) case params.RaftStoreType: - return ridraftstore.Init(ctx, logger) + return ridraftstore.Init(ctx, logger, locality) case params.MemStoreType: return ridmemstore.Init(ctx, logger) default: diff --git a/pkg/scd/store/raftstore/store.go b/pkg/scd/store/raftstore/store.go index 051670e35..6b766f988 100644 --- a/pkg/scd/store/raftstore/store.go +++ b/pkg/scd/store/raftstore/store.go @@ -15,12 +15,12 @@ import ( // repo is a full implementation of scd.repos.Repository for Raft-based storage. type repo struct{} -func Init(ctx context.Context, logger *zap.Logger) (*raftstore.Store[repos.Repository], error) { +func Init(ctx context.Context, logger *zap.Logger, locality string) (*raftstore.Store[repos.Repository], error) { params, err := scdraftparams.GetConnectParameters() if err != nil { return nil, stacktrace.Propagate(err, "failed to get scd raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "scd")), params, &repo{}) + return raftstore.Init(ctx, logger.With(zap.String("service", "scd")), locality, params, &repo{}) } func (r *repo) GetRepo() repos.Repository { return r } diff --git a/pkg/scd/store/store.go b/pkg/scd/store/store.go index 0cdc48b93..6a6b70665 100644 --- a/pkg/scd/store/store.go +++ b/pkg/scd/store/store.go @@ -18,13 +18,13 @@ import ( type Store = dssstore.Store[repos.Repository] // Init selects and initializes the scd store backend. -func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool) (Store, error) { +func Init(ctx context.Context, logger *zap.Logger, withCheckCron bool, locality string) (Store, error) { storeType := params.GetStoreParameters().StoreType switch storeType { case params.SQLStoreType: return scdsqlstore.Init(ctx, logger, withCheckCron) case params.RaftStoreType: - return scdraftstore.Init(ctx, logger) + return scdraftstore.Init(ctx, logger, locality) case params.MemStoreType: return scdmemstore.Init(ctx, logger) default: From f07a912d09377e555e388d71881c47979a3a4acd Mon Sep 17 00:00:00 2001 From: Mariem Baccari Date: Tue, 30 Jun 2026 14:04:14 +0200 Subject: [PATCH 7/7] [raft/aux] Implement aux Raftstore --- pkg/aux_/actions/pool_participants.go | 78 +++++++++++++++++++++++ pkg/aux_/actions/registry.go | 33 +++++++++- pkg/aux_/pool_participants.go | 92 ++++----------------------- pkg/aux_/store/memstore/dss.go | 4 +- pkg/aux_/store/raftstore/dss.go | 73 ++++++++++++++++++--- pkg/aux_/store/raftstore/store.go | 78 ++++++++++++++++++++--- pkg/raftstore/consensus/consensus.go | 2 +- pkg/raftstore/consensus/proposal.go | 10 +-- pkg/raftstore/params/params.go | 4 ++ pkg/raftstore/store.go | 22 +++++-- pkg/rid/store/raftstore/store.go | 3 +- pkg/scd/store/raftstore/store.go | 3 +- pkg/sqlstore/store.go | 1 + pkg/store/store.go | 28 +++----- 14 files changed, 295 insertions(+), 136 deletions(-) create mode 100644 pkg/aux_/actions/pool_participants.go diff --git a/pkg/aux_/actions/pool_participants.go b/pkg/aux_/actions/pool_participants.go new file mode 100644 index 000000000..d3e600e85 --- /dev/null +++ b/pkg/aux_/actions/pool_participants.go @@ -0,0 +1,78 @@ +package actions + +import ( + "context" + "time" + + restapi "github.com/interuss/dss/pkg/api/auxv1" + auxmodels "github.com/interuss/dss/pkg/aux_/models" + "github.com/interuss/dss/pkg/aux_/repos" + dsserr "github.com/interuss/dss/pkg/errors" + "github.com/interuss/dss/pkg/locality" + "github.com/interuss/stacktrace" +) + +func GetDSSInstances(ctx context.Context, r repos.Repository, _ *restapi.GetDSSInstancesRequest) (any, error) { + metadata, err := r.GetDSSMetadata(ctx) + if err != nil { + return nil, err + } + + instances := make([]restapi.DSSInstance, len(metadata)) + for index, instanceMetadata := range metadata { + instances[index] = restapi.DSSInstance{ + Id: instanceMetadata.Locality, + PublicEndpoint: &instanceMetadata.PublicEndpoint, + } + + if instanceMetadata.LatestTimestamp.Source.Valid { + instances[index].MostRecentHeartbeat = &restapi.Heartbeat{ + Timestamp: instanceMetadata.LatestTimestamp.Timestamp.Format(time.RFC3339Nano), + Reporter: &instanceMetadata.LatestTimestamp.Reporter.String, + Source: instanceMetadata.LatestTimestamp.Source.String, + } + + if instanceMetadata.LatestTimestamp.NextHeartbeatExpectedBefore != nil { + nextExpectedTimestamp := instanceMetadata.LatestTimestamp.NextHeartbeatExpectedBefore.Format(time.RFC3339Nano) + instances[index].MostRecentHeartbeat.NextHeartbeatExpectedBefore = &nextExpectedTimestamp + } + } + } + + return &restapi.DSSInstancesResponse{DssInstances: &instances}, nil +} + +func PutDSSInstancesHeartbeat(ctx context.Context, r repos.Repository, a *restapi.PutDSSInstancesHeartbeatRequest) (any, error) { + if a.Source == nil { + return nil, stacktrace.NewErrorWithCode(dsserr.BadRequest, "Source not set") + } + + locality, err := locality.LocalityFromContext(ctx) + if err != nil { + return nil, stacktrace.Propagate(err, "Unable to get request locality") + } + + heartbeat := auxmodels.Heartbeat{ + Source: *a.Source, + Reporter: *a.Auth.ClientID, + Locality: locality, + } + + if a.Timestamp != nil { + ts, err := time.Parse(time.RFC3339Nano, *a.Timestamp) + if err != nil { + return nil, stacktrace.PropagateWithCode(err, dsserr.BadRequest, "Unable to parse timestamp as RFC3339 time") + } + heartbeat.Timestamp = &ts + } + + if a.NextHeartbeatExpectedBefore != nil { + ts, err := time.Parse(time.RFC3339Nano, *a.NextHeartbeatExpectedBefore) + if err != nil { + return nil, stacktrace.PropagateWithCode(err, dsserr.BadRequest, "Unable to parse next heartbeat expected before as RFC3339 time") + } + heartbeat.NextHeartbeatExpectedBefore = &ts + } + + return nil, r.RecordHeartbeat(ctx, heartbeat) +} diff --git a/pkg/aux_/actions/registry.go b/pkg/aux_/actions/registry.go index d16395b88..0004f95b5 100644 --- a/pkg/aux_/actions/registry.go +++ b/pkg/aux_/actions/registry.go @@ -1,10 +1,37 @@ package actions import ( + "context" + "encoding/json" + + restapi "github.com/interuss/dss/pkg/api/auxv1" "github.com/interuss/dss/pkg/aux_/repos" dssstore "github.com/interuss/dss/pkg/store" ) -// Registry maps operation IDs to their handlers. -// TODO: implement -var Registry = map[string]dssstore.OperationHandler[repos.Repository]{} +var Registry = map[string]dssstore.OperationHandler[repos.Repository]{ + restapi.GetDSSInstancesOperationID: { + Marshal: func(req dssstore.OperationRequest) ([]byte, error) { + return json.Marshal(req.(*restapi.GetDSSInstancesRequest)) + }, + Unmarshal: func(data []byte) (dssstore.OperationRequest, error) { + req := &restapi.GetDSSInstancesRequest{} + return req, json.Unmarshal(data, req) + }, + Execute: func(ctx context.Context, r repos.Repository, req dssstore.OperationRequest) (any, error) { + return GetDSSInstances(ctx, r, req.(*restapi.GetDSSInstancesRequest)) + }, + }, + restapi.PutDSSInstancesHeartbeatOperationID: { + Marshal: func(req dssstore.OperationRequest) ([]byte, error) { + return json.Marshal(req.(*restapi.PutDSSInstancesHeartbeatRequest)) + }, + Unmarshal: func(data []byte) (dssstore.OperationRequest, error) { + req := &restapi.PutDSSInstancesHeartbeatRequest{} + return req, json.Unmarshal(data, req) + }, + Execute: func(ctx context.Context, r repos.Repository, req dssstore.OperationRequest) (any, error) { + return PutDSSInstancesHeartbeat(ctx, r, req.(*restapi.PutDSSInstancesHeartbeatRequest)) + }, + }, +} diff --git a/pkg/aux_/pool_participants.go b/pkg/aux_/pool_participants.go index 657678e6e..4495bf3c5 100644 --- a/pkg/aux_/pool_participants.go +++ b/pkg/aux_/pool_participants.go @@ -2,12 +2,13 @@ package aux import ( "context" - "time" "github.com/interuss/dss/pkg/api" restapi "github.com/interuss/dss/pkg/api/auxv1" - "github.com/interuss/dss/pkg/aux_/models" + "github.com/interuss/dss/pkg/aux_/repos" dsserr "github.com/interuss/dss/pkg/errors" + "github.com/interuss/dss/pkg/locality" + "github.com/interuss/dss/pkg/store" "github.com/interuss/stacktrace" ) @@ -15,13 +16,7 @@ func (a *Server) GetDSSInstances(ctx context.Context, req *restapi.GetDSSInstanc resp := restapi.GetDSSInstancesResponseSet{} - repo, err := a.Store.Interact(ctx) - if err != nil { - resp.Response500 = &api.InternalServerErrorBody{ErrorMessage: *dsserr.Handle(ctx, stacktrace.Propagate(err, "Unable to interact with the store"))} - return resp - } - - metadata, err := repo.GetDSSMetadata(ctx) + response, err := store.TransactWithResult[repos.Repository, *restapi.DSSInstancesResponse](ctx, a.Store, req) if err != nil { switch stacktrace.GetCode(err) { @@ -34,88 +29,29 @@ func (a *Server) GetDSSInstances(ctx context.Context, req *restapi.GetDSSInstanc return resp } - instances := make([]restapi.DSSInstance, len(metadata)) - - for index, instanceMetadata := range metadata { - - instances[index] = restapi.DSSInstance{ - Id: instanceMetadata.Locality, - PublicEndpoint: &instanceMetadata.PublicEndpoint, - } - - if instanceMetadata.LatestTimestamp.Source.Valid { - - instances[index].MostRecentHeartbeat = &restapi.Heartbeat{ - Timestamp: instanceMetadata.LatestTimestamp.Timestamp.Format(time.RFC3339Nano), - Reporter: &instanceMetadata.LatestTimestamp.Reporter.String, - Source: instanceMetadata.LatestTimestamp.Source.String, - } - - if instanceMetadata.LatestTimestamp.NextHeartbeatExpectedBefore != nil { - nextExpectedTimestamp := instanceMetadata.LatestTimestamp.NextHeartbeatExpectedBefore.Format(time.RFC3339Nano) - instances[index].MostRecentHeartbeat.NextHeartbeatExpectedBefore = &nextExpectedTimestamp - } - - } - - } - - resp.Response200 = &restapi.DSSInstancesResponse{DssInstances: &instances} + resp.Response200 = response return resp } func (a *Server) PutDSSInstancesHeartbeat(ctx context.Context, req *restapi.PutDSSInstancesHeartbeatRequest) restapi.PutDSSInstancesHeartbeatResponseSet { + ctx = locality.WithLocality(ctx, a.Locality) - resp := restapi.PutDSSInstancesHeartbeatResponseSet{} - - repo, err := a.Store.Interact(ctx) + _, err := a.Store.Transact(ctx, req) if err != nil { - resp.Response500 = &api.InternalServerErrorBody{ErrorMessage: *dsserr.Handle(ctx, stacktrace.Propagate(err, "Unable to interact with the store"))} - return resp - } - - if req.Source == nil { - resp.Response400 = &restapi.ErrorResponse{Message: dsserr.Handle(ctx, stacktrace.Propagate(err, "Source not set"))} - return resp - } - - heartbeat := models.Heartbeat{ - Source: *req.Source, - Reporter: *req.Auth.ClientID, - Locality: a.Locality, - } - - if req.Timestamp != nil { - ts, err := time.Parse(time.RFC3339Nano, *req.Timestamp) - if err != nil { - resp.Response400 = &restapi.ErrorResponse{Message: dsserr.Handle(ctx, stacktrace.Propagate(err, "Unable to parse timestamp as RFC3339 time"))} - return resp - } - heartbeat.Timestamp = &ts - } - - if req.NextHeartbeatExpectedBefore != nil { - ts, err := time.Parse(time.RFC3339Nano, *req.NextHeartbeatExpectedBefore) - if err != nil { - resp.Response400 = &restapi.ErrorResponse{Message: dsserr.Handle(ctx, stacktrace.Propagate(err, "Unable to parse next heartbeat expected before as RFC3339 time"))} - return resp + switch stacktrace.GetCode(err) { + case dsserr.BadRequest: + return restapi.PutDSSInstancesHeartbeatResponseSet{Response400: &restapi.ErrorResponse{ + Message: dsserr.Handle(ctx, stacktrace.Propagate(err, "Invalid heartbeat"))}} + default: + return restapi.PutDSSInstancesHeartbeatResponseSet{Response400: &restapi.ErrorResponse{ + Message: dsserr.Handle(ctx, stacktrace.Propagate(err, "Unable to record heartbeat"))}} } - heartbeat.NextHeartbeatExpectedBefore = &ts - } - - err = repo.RecordHeartbeat(ctx, heartbeat) - - if err != nil { - resp.Response400 = &restapi.ErrorResponse{Message: dsserr.Handle(ctx, stacktrace.Propagate(err, "Unable to record heartbeat"))} - return resp } // Return the same response as the get one - getResponse := a.GetDSSInstances(ctx, &restapi.GetDSSInstancesRequest{}) - return restapi.PutDSSInstancesHeartbeatResponseSet{ Response201: getResponse.Response200, Response401: getResponse.Response401, diff --git a/pkg/aux_/store/memstore/dss.go b/pkg/aux_/store/memstore/dss.go index 38fa4d5bf..2070e59d9 100644 --- a/pkg/aux_/store/memstore/dss.go +++ b/pkg/aux_/store/memstore/dss.go @@ -8,7 +8,7 @@ import ( "github.com/interuss/stacktrace" ) -func (r *repo) SaveOwnMetadata(_ context.Context, locality string, publicEndpoint string) error { +func (r *repo) SaveOwnMetadata(_ context.Context, _ string, _ string) error { return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "SaveOwnMetadata not implemented for memstore") } @@ -16,7 +16,7 @@ func (r *repo) GetDSSMetadata(_ context.Context) ([]*auxmodels.DSSMetadata, erro return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "GetDSSMetadata not implemented for memstore") } -func (r *repo) RecordHeartbeat(_ context.Context, heartbeat auxmodels.Heartbeat) error { +func (r *repo) RecordHeartbeat(_ context.Context, _ auxmodels.Heartbeat) error { return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "RecordHeartbeat not implemented for memstore") } diff --git a/pkg/aux_/store/raftstore/dss.go b/pkg/aux_/store/raftstore/dss.go index 871d06394..6cbdabbf0 100644 --- a/pkg/aux_/store/raftstore/dss.go +++ b/pkg/aux_/store/raftstore/dss.go @@ -2,25 +2,82 @@ package raftstore import ( "context" + "encoding/json" + "strconv" auxmodels "github.com/interuss/dss/pkg/aux_/models" dsserr "github.com/interuss/dss/pkg/errors" + raftparams "github.com/interuss/dss/pkg/raftstore/params" + "github.com/interuss/dss/pkg/timestamp" "github.com/interuss/stacktrace" ) -// SaveOwnMetadata returns nil instead of dsserr.NotImplemented because it is needed to allow the server to startup. -func (r *repo) SaveOwnMetadata(_ context.Context, locality string, publicEndpoint string) error { - return nil +type saveOwnMetadataPayload struct { + Locality string + PublicEndpoint string } -func (r *repo) GetDSSMetadata(_ context.Context) ([]*auxmodels.DSSMetadata, error) { - return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "GetDSSMetadata not implemented for raftstore") +func (r *repo) SaveOwnMetadata(ctx context.Context, locality string, publicEndpoint string) error { + if locality == "" { + return stacktrace.NewErrorWithCode(dsserr.BadRequest, "Locality not set") + } + if publicEndpoint == "" { + return stacktrace.NewErrorWithCode(dsserr.BadRequest, "Public endpoint not set") + } + + payload, err := json.Marshal(saveOwnMetadataPayload{ + Locality: locality, + PublicEndpoint: publicEndpoint, + }) + if err != nil { + return stacktrace.Propagate(err, "failed to marshal %s payload", saveOwnMetadata) + } + + _, err = r.consensus.HandleClientRequest(ctx, saveOwnMetadata, payload, false) + return err } -func (r *repo) RecordHeartbeat(_ context.Context, heartbeat auxmodels.Heartbeat) error { - return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "RecordHeartbeat not implemented for raftstore") +func (r *repo) GetDSSMetadata(ctx context.Context) ([]*auxmodels.DSSMetadata, error) { + payload, err := r.consensus.HandleClientRequest(ctx, getDSSMetadata, nil, true) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to propose %s", getDSSMetadata) + } + if payload == nil { + return nil, nil + } + return payload.([]*auxmodels.DSSMetadata), nil +} + +func (r *repo) RecordHeartbeat(ctx context.Context, heartbeat auxmodels.Heartbeat) error { + if heartbeat.Locality == "" { + return stacktrace.NewErrorWithCode(dsserr.BadRequest, "Locality not set") + } + if heartbeat.Source == "" { + return stacktrace.NewErrorWithCode(dsserr.BadRequest, "Source not set") + } + + if heartbeat.Timestamp == nil { + now, err := timestamp.RequestTimestampFromContext(ctx) + if err != nil { + return stacktrace.Propagate(err, "failed to get request timestamp") + } + + heartbeat.Timestamp = &now + } + + if heartbeat.NextHeartbeatExpectedBefore != nil && heartbeat.NextHeartbeatExpectedBefore.Before(*heartbeat.Timestamp) { + return stacktrace.NewErrorWithCode(dsserr.BadRequest, "Cannot expect the timestamp of the next heartbeat before the timestamp of the new heartbeat") + } + + data, err := json.Marshal(heartbeat) + if err != nil { + return stacktrace.Propagate(err, "failed to marshal %s payload", recordHeartbeat) + } + + _, err = r.consensus.HandleClientRequest(ctx, recordHeartbeat, data, false) + return err } func (r *repo) GetDSSAirspaceRepresentationID(_ context.Context) (string, error) { - return "", stacktrace.NewErrorWithCode(dsserr.NotImplemented, "GetDSSAirspaceRepresentationID not implemented for raftstore") + return strconv.Itoa(int(raftparams.GetClusterID())), nil } diff --git a/pkg/aux_/store/raftstore/store.go b/pkg/aux_/store/raftstore/store.go index e1db50062..7198b928c 100644 --- a/pkg/aux_/store/raftstore/store.go +++ b/pkg/aux_/store/raftstore/store.go @@ -2,37 +2,99 @@ package raftstore import ( "context" + "encoding/json" + "github.com/interuss/dss/pkg/aux_/actions" + auxmodels "github.com/interuss/dss/pkg/aux_/models" "github.com/interuss/dss/pkg/aux_/repos" + auxmemstore "github.com/interuss/dss/pkg/aux_/store/memstore" auxraftparams "github.com/interuss/dss/pkg/aux_/store/raftstore/params" - dsserr "github.com/interuss/dss/pkg/errors" + "github.com/interuss/dss/pkg/memstore" "github.com/interuss/dss/pkg/raftstore" "github.com/interuss/dss/pkg/raftstore/consensus" "github.com/interuss/stacktrace" "go.uber.org/zap" ) +const ( + saveOwnMetadata = "saveOwnMetadata" + getDSSMetadata = "getDSSMetadata" + recordHeartbeat = "recordHeartbeat" +) + // repo is a full implementation of aux_.repos.Repository for Raft-based storage. -type repo struct{} +type repo struct { + consensus *consensus.Consensus + memStore *memstore.Store[repos.Repository] +} func Init(ctx context.Context, logger *zap.Logger, locality string) (*raftstore.Store[repos.Repository], error) { params, err := auxraftparams.GetConnectParameters() if err != nil { return nil, stacktrace.Propagate(err, "failed to get aux raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "aux_")), locality, params, &repo{}) + + memStore, err := auxmemstore.Init(ctx, logger) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to initialize aux memstore") + } + + r := &repo{memStore: memStore} + + store, err := raftstore.Init(ctx, logger.With(zap.String("service", "aux_")), locality, params, r, actions.Registry) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to initialize aux raftstore") + } + + r.consensus = store.Consensus + return store, nil } func (r *repo) GetRepo() repos.Repository { return r } func (r *repo) GetSnapshot() ([]byte, error) { - return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") + return nil, stacktrace.NewError("not implemented yet") } -func (r *repo) RestoreFromSnapshot([]byte) error { - return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +func (r *repo) RestoreFromSnapshot(_ []byte) error { + return stacktrace.NewError("not implemented yet") } -func (r *repo) Apply(_ context.Context, _ consensus.Proposal) (any, error) { - return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "not implemented yet") +func (r *repo) Apply(ctx context.Context, proposal consensus.Proposal) (any, error) { + memRepo, err := r.memStore.Interact(ctx) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to obtain aux memstore repository") + } + + // API-level operations: dispatch via registry. + if handler, ok := actions.Registry[proposal.RequestType]; ok { + req, err := handler.Unmarshal(proposal.Value) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to unmarshal op %q", proposal.RequestType) + } + return handler.Execute(ctx, memRepo, req) + } + + // Internal repo operations that go through consensus directly. + switch proposal.RequestType { + case saveOwnMetadata: + var p saveOwnMetadataPayload + if err := json.Unmarshal(proposal.Value, &p); err != nil { + return nil, stacktrace.Propagate(err, "failed to unmarshal %s payload", saveOwnMetadata) + } + return nil, memRepo.SaveOwnMetadata(ctx, p.Locality, p.PublicEndpoint) + + case getDSSMetadata: + return memRepo.GetDSSMetadata(ctx) + + case recordHeartbeat: + var hb auxmodels.Heartbeat + if err := json.Unmarshal(proposal.Value, &hb); err != nil { + return nil, stacktrace.Propagate(err, "failed to unmarshal %s payload", recordHeartbeat) + } + return nil, memRepo.RecordHeartbeat(ctx, hb) + + default: + return nil, stacktrace.NewError("unknown or unsupported request type: %q", proposal.RequestType) + } } diff --git a/pkg/raftstore/consensus/consensus.go b/pkg/raftstore/consensus/consensus.go index 5ede3e396..2f216c301 100644 --- a/pkg/raftstore/consensus/consensus.go +++ b/pkg/raftstore/consensus/consensus.go @@ -132,7 +132,7 @@ func (c *Consensus) Stop(ctx context.Context) { } // HandleClientRequest blocks until the proposal is committed and applied / dropped or until ctx is cancelled. -func (c *Consensus) HandleClientRequest(ctx context.Context, requestType string, value any, readOnly bool) (any, error) { +func (c *Consensus) HandleClientRequest(ctx context.Context, requestType string, value []byte, readOnly bool) (any, error) { proposal, err := c.newProposal(ctx, requestType, value, readOnly) if err != nil { return nil, stacktrace.Propagate(err, "failed to create proposal") diff --git a/pkg/raftstore/consensus/proposal.go b/pkg/raftstore/consensus/proposal.go index efdc24fea..29b848998 100644 --- a/pkg/raftstore/consensus/proposal.go +++ b/pkg/raftstore/consensus/proposal.go @@ -2,7 +2,6 @@ package consensus import ( "context" - "encoding/json" "sync" "time" @@ -32,24 +31,19 @@ type Proposal struct { ReadOnly bool `json:"read_only"` } -func (c *Consensus) newProposal(ctx context.Context, requestType string, payload any, readOnly bool) (Proposal, error) { +func (c *Consensus) newProposal(ctx context.Context, requestType string, payload []byte, readOnly bool) (Proposal, error) { timestamp, err := timestamp.RequestTimestampFromContext(ctx) if err != nil || timestamp.IsZero() { return Proposal{}, stacktrace.Propagate(err, "failed to get timestamp from context") } - value, err := json.Marshal(payload) - if err != nil { - return Proposal{}, stacktrace.Propagate(err, "failed to serialize proposal payload") - } - return Proposal{ ID: uuid.NewString(), Locality: c.locality, NodeID: c.nodeID, Timestamp: timestamp, RequestType: requestType, - Value: value, + Value: payload, ReadOnly: readOnly, }, nil } diff --git a/pkg/raftstore/params/params.go b/pkg/raftstore/params/params.go index c7acb6ef4..70b9feffa 100644 --- a/pkg/raftstore/params/params.go +++ b/pkg/raftstore/params/params.go @@ -150,3 +150,7 @@ func GetConnectParameters(subfolder string) (ConnectParameters, error) { p.DataDir = filepath.Join(connectParameters.DataDir, subfolder) return p, nil } + +func GetClusterID() uint64 { + return connectParameters.ClusterID +} diff --git a/pkg/raftstore/store.go b/pkg/raftstore/store.go index a3f69d891..364c3d8ce 100644 --- a/pkg/raftstore/store.go +++ b/pkg/raftstore/store.go @@ -4,6 +4,7 @@ import ( "context" "strings" + "github.com/interuss/dss/pkg/locality" "github.com/interuss/dss/pkg/logging" "github.com/interuss/dss/pkg/raftstore/consensus" raftparams "github.com/interuss/dss/pkg/raftstore/params" @@ -27,34 +28,40 @@ type Store[R any] struct { name string raftRepo RaftRepo[R] cancel context.CancelFunc + registry map[string]store.OperationHandler[R] Consensus *consensus.Consensus } -func Init[R any](ctx context.Context, logger *zap.Logger, locality string, params raftparams.ConnectParameters, r RaftRepo[R]) (*Store[R], error) { +func Init[R any](ctx context.Context, logger *zap.Logger, locality string, params raftparams.ConnectParameters, r RaftRepo[R], registry map[string]store.OperationHandler[R]) (*Store[R], error) { ctx, cancel := context.WithCancel(ctx) - store := &Store[R]{ + s := &Store[R]{ raftRepo: r, logger: logging.WithValuesFromContext(ctx, logger), cancel: cancel, + registry: registry, } commitC := make(chan consensus.EntryCommit) - go store.processCommits(ctx, commitC) + go s.processCommits(ctx, commitC) consensusInstance, err := consensus.NewConsensus(ctx, logger, locality, params, r.GetSnapshot, commitC) if err != nil { return nil, stacktrace.Propagate(err, "failed to initialize consensus") } - store.Consensus = consensusInstance + s.Consensus = consensusInstance - return store, nil + return s, nil } -// Transact proposes the entry to Raft and blocks until it is committed and applied. +// Transact serializes op via its registered handler and proposes it to Raft. func (s *Store[R]) Transact(ctx context.Context, op store.OperationRequest) (any, error) { - payload, err := op.MarshalBinary() + handler, ok := s.registry[op.OperationID()] + if !ok { + return nil, stacktrace.NewError("no handler registered for operation %q", op.OperationID()) + } + payload, err := handler.Marshal(op) if err != nil { return nil, stacktrace.Propagate(err, "failed to marshal op %q", op.OperationID()) } @@ -102,6 +109,7 @@ func (s *Store[R]) processCommits(ctx context.Context, commitCh <-chan consensus } ctx = timestamp.WithRequestTimestamp(ctx, commit.Prop.Timestamp) + ctx = locality.WithLocality(ctx, commit.Prop.Locality) result, err := s.raftRepo.Apply(ctx, commit.Prop) commit.Done <- consensus.ProposalResult{Result: result, Error: err} } diff --git a/pkg/rid/store/raftstore/store.go b/pkg/rid/store/raftstore/store.go index 0cc69cbb2..a9c69a50a 100644 --- a/pkg/rid/store/raftstore/store.go +++ b/pkg/rid/store/raftstore/store.go @@ -6,6 +6,7 @@ import ( dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/raftstore" "github.com/interuss/dss/pkg/raftstore/consensus" + "github.com/interuss/dss/pkg/rid/actions" "github.com/interuss/dss/pkg/rid/repos" ridraftparams "github.com/interuss/dss/pkg/rid/store/raftstore/params" "github.com/interuss/stacktrace" @@ -20,7 +21,7 @@ func Init(ctx context.Context, logger *zap.Logger, locality string) (*raftstore. if err != nil { return nil, stacktrace.Propagate(err, "failed to get rid raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "rid")), locality, params, &repo{}) + return raftstore.Init(ctx, logger.With(zap.String("service", "rid")), locality, params, &repo{}, actions.Registry) } func (r *repo) GetRepo() repos.Repository { return r } diff --git a/pkg/scd/store/raftstore/store.go b/pkg/scd/store/raftstore/store.go index 6b766f988..96b30c657 100644 --- a/pkg/scd/store/raftstore/store.go +++ b/pkg/scd/store/raftstore/store.go @@ -6,6 +6,7 @@ import ( dsserr "github.com/interuss/dss/pkg/errors" "github.com/interuss/dss/pkg/raftstore" "github.com/interuss/dss/pkg/raftstore/consensus" + "github.com/interuss/dss/pkg/scd/actions" "github.com/interuss/dss/pkg/scd/repos" scdraftparams "github.com/interuss/dss/pkg/scd/store/raftstore/params" "github.com/interuss/stacktrace" @@ -20,7 +21,7 @@ func Init(ctx context.Context, logger *zap.Logger, locality string) (*raftstore. if err != nil { return nil, stacktrace.Propagate(err, "failed to get scd raft parameters") } - return raftstore.Init(ctx, logger.With(zap.String("service", "scd")), locality, params, &repo{}) + return raftstore.Init(ctx, logger.With(zap.String("service", "scd")), locality, params, &repo{}, actions.Registry) } func (r *repo) GetRepo() repos.Repository { return r } diff --git a/pkg/sqlstore/store.go b/pkg/sqlstore/store.go index 674a1abd0..3fc2067a9 100644 --- a/pkg/sqlstore/store.go +++ b/pkg/sqlstore/store.go @@ -191,6 +191,7 @@ func (s *Store[R]) execute(ctx context.Context, repo R, op store.OperationReques return handler.Execute(ctx, repo, op) } + func (s *Store[R]) Transact(ctx context.Context, op store.OperationRequest) (any, error) { if s.Version.Type == Yugabyte { return s.transactYugabyte(ctx, op) diff --git a/pkg/store/store.go b/pkg/store/store.go index 0dd2ecdff..dfc878c53 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -2,33 +2,29 @@ package store import ( "context" - "encoding" "io" "github.com/interuss/stacktrace" ) -// OperationRequest represents a request to be performed on a Store. +// OperationRequest identifies an operation to be performed on a Store. type OperationRequest interface { - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler - OperationID() string } -// OperationHandler holds the business logic for a registered operation. +// OperationHandler holds the logic for the encoding and execution of a registered operation type OperationHandler[R any] struct { - New func() OperationRequest - Execute func(ctx context.Context, r R, req OperationRequest) (any, error) + Marshal func(req OperationRequest) ([]byte, error) + Unmarshal func(buf []byte) (OperationRequest, error) + Execute func(ctx context.Context, r R, req OperationRequest) (any, error) } -// store.Store is the generic means to access and interact with any type of data backing the DSS +// Store is the generic means to access and interact with any type of data backing the DSS. type Store[R any] interface { io.Closer - // Obtain a Repo (repo type R) that doesn't need transactional guarantees (for instance, - // read-only). + // Interact obtains a Repo that doesn't need transactional guarantees (e.g. read-only). Interact(context.Context) (R, error) - // Attempt to apply the operation op to the R Repo atomically. + // Transact applies the operation op to the R Repo atomically. Transact(ctx context.Context, op OperationRequest) (any, error) } @@ -56,13 +52,7 @@ func NewFuncOperation[R any](f func(context.Context, R) error) *FuncOperation[R] return &FuncOperation[R]{f: f} } -func (a *FuncOperation[R]) OperationID() string { return "" } -func (a *FuncOperation[R]) MarshalBinary() ([]byte, error) { - return nil, stacktrace.NewError("FuncOperation cannot be serialized") -} -func (a *FuncOperation[R]) UnmarshalBinary(_ []byte) error { - return stacktrace.NewError("FuncOperation cannot be deserialized") -} +func (a *FuncOperation[R]) OperationID() string { return "" } func (a *FuncOperation[R]) Execute(ctx context.Context, r R) error { return a.f(ctx, r) } const (