Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/memstore/utils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package utils

func ClonePtr[T any](v *T) *T {
if v == nil {
return nil
}
return new(*v)
}
17 changes: 9 additions & 8 deletions pkg/rid/store/memstore/identification_service_area.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/golang/geo/s2"
dsserr "github.com/interuss/dss/pkg/errors"
"github.com/interuss/dss/pkg/memstore/utils"
dssmodels "github.com/interuss/dss/pkg/models"
ridmodels "github.com/interuss/dss/pkg/rid/models"
"github.com/interuss/dss/pkg/timestamp"
Expand All @@ -19,10 +20,10 @@ func isaRecordFromModel(isa *ridmodels.IdentificationServiceArea, updatedAt time
URL: isa.URL,
Owner: isa.Owner,
Cells: slices.Clone(isa.Cells),
StartTime: clonePtr(isa.StartTime),
EndTime: clonePtr(isa.EndTime),
AltitudeHi: clonePtr(isa.AltitudeHi),
AltitudeLo: clonePtr(isa.AltitudeLo),
StartTime: utils.ClonePtr(isa.StartTime),
EndTime: utils.ClonePtr(isa.EndTime),
AltitudeHi: utils.ClonePtr(isa.AltitudeHi),
AltitudeLo: utils.ClonePtr(isa.AltitudeLo),
Writer: isa.Writer,
UpdatedAt: updatedAt,
}
Expand All @@ -35,11 +36,11 @@ func (rec *isaRecord) toModel() *ridmodels.IdentificationServiceArea {
URL: rec.URL,
Owner: rec.Owner,
Cells: slices.Clone(rec.Cells),
StartTime: clonePtr(rec.StartTime),
EndTime: clonePtr(rec.EndTime),
StartTime: utils.ClonePtr(rec.StartTime),
EndTime: utils.ClonePtr(rec.EndTime),
Version: dssmodels.VersionFromTime(rec.UpdatedAt),
AltitudeHi: clonePtr(rec.AltitudeHi),
AltitudeLo: clonePtr(rec.AltitudeLo),
AltitudeHi: utils.ClonePtr(rec.AltitudeHi),
AltitudeLo: utils.ClonePtr(rec.AltitudeLo),
Writer: rec.Writer,
}
}
Expand Down
24 changes: 9 additions & 15 deletions pkg/rid/store/memstore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/golang/geo/s2"
"github.com/interuss/dss/pkg/geo"
"github.com/interuss/dss/pkg/memstore"
"github.com/interuss/dss/pkg/memstore/utils"
dssmodels "github.com/interuss/dss/pkg/models"
"github.com/interuss/dss/pkg/rid/repos"
"github.com/interuss/stacktrace"
Expand Down Expand Up @@ -117,13 +118,6 @@ func overlaps(cells s2.CellUnion, set map[s2.CellID]struct{}) bool {
return false
}

func clonePtr[T any](v *T) *T {
if v == nil {
return nil
}
return new(*v)
}

type versionedRecord interface {
version() *dssmodels.Version
}
Expand Down Expand Up @@ -182,20 +176,20 @@ func listExpired[M any, R expiringRecord[M]](store map[dssmodels.ID]R, writer st
func (rec *isaRecord) clone() *isaRecord {
cp := *rec
cp.Cells = slices.Clone(rec.Cells)
cp.StartTime = clonePtr(rec.StartTime)
cp.EndTime = clonePtr(rec.EndTime)
cp.AltitudeHi = clonePtr(rec.AltitudeHi)
cp.AltitudeLo = clonePtr(rec.AltitudeLo)
cp.StartTime = utils.ClonePtr(rec.StartTime)
cp.EndTime = utils.ClonePtr(rec.EndTime)
cp.AltitudeHi = utils.ClonePtr(rec.AltitudeHi)
cp.AltitudeLo = utils.ClonePtr(rec.AltitudeLo)
return &cp
}

func (rec *subscriptionRecord) clone() *subscriptionRecord {
cp := *rec
cp.Cells = slices.Clone(rec.Cells)
cp.StartTime = clonePtr(rec.StartTime)
cp.EndTime = clonePtr(rec.EndTime)
cp.AltitudeHi = clonePtr(rec.AltitudeHi)
cp.AltitudeLo = clonePtr(rec.AltitudeLo)
cp.StartTime = utils.ClonePtr(rec.StartTime)
cp.EndTime = utils.ClonePtr(rec.EndTime)
cp.AltitudeHi = utils.ClonePtr(rec.AltitudeHi)
cp.AltitudeLo = utils.ClonePtr(rec.AltitudeLo)
return &cp
}

Expand Down
20 changes: 12 additions & 8 deletions pkg/rid/store/memstore/subscriptions.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package memstore

// Note: as of now, doesn't implement timeBasedNotificationIndex settings, as it doesn't improve performance
// and was done mostly for SQL store improvements.

import (
"context"
"iter"
Expand All @@ -9,6 +12,7 @@ import (

"github.com/golang/geo/s2"
dsserr "github.com/interuss/dss/pkg/errors"
"github.com/interuss/dss/pkg/memstore/utils"
dssmodels "github.com/interuss/dss/pkg/models"
ridmodels "github.com/interuss/dss/pkg/rid/models"
"github.com/interuss/dss/pkg/timestamp"
Expand All @@ -22,10 +26,10 @@ func subRecordFromModel(s *ridmodels.Subscription, updatedAt time.Time) *subscri
NotificationIndex: s.NotificationIndex,
Owner: s.Owner,
Cells: slices.Clone(s.Cells),
StartTime: clonePtr(s.StartTime),
EndTime: clonePtr(s.EndTime),
AltitudeHi: clonePtr(s.AltitudeHi),
AltitudeLo: clonePtr(s.AltitudeLo),
StartTime: utils.ClonePtr(s.StartTime),
EndTime: utils.ClonePtr(s.EndTime),
AltitudeHi: utils.ClonePtr(s.AltitudeHi), // TODO: As noted during review, altitudes seems unused.
AltitudeLo: utils.ClonePtr(s.AltitudeLo),
Writer: s.Writer,
UpdatedAt: updatedAt,
}
Expand All @@ -38,11 +42,11 @@ func (rec *subscriptionRecord) toModel() *ridmodels.Subscription {
NotificationIndex: rec.NotificationIndex,
Owner: rec.Owner,
Cells: slices.Clone(rec.Cells),
StartTime: clonePtr(rec.StartTime),
EndTime: clonePtr(rec.EndTime),
StartTime: utils.ClonePtr(rec.StartTime),
EndTime: utils.ClonePtr(rec.EndTime),
Version: dssmodels.VersionFromTime(rec.UpdatedAt),
AltitudeHi: clonePtr(rec.AltitudeHi),
AltitudeLo: clonePtr(rec.AltitudeLo),
AltitudeHi: utils.ClonePtr(rec.AltitudeHi),
AltitudeLo: utils.ClonePtr(rec.AltitudeLo),
Writer: rec.Writer,
}
}
Expand Down
30 changes: 25 additions & 5 deletions pkg/scd/store/memstore/availability.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,36 @@ package memstore
import (
"context"

dsserr "github.com/interuss/dss/pkg/errors"
dssmodels "github.com/interuss/dss/pkg/models"
scdmodels "github.com/interuss/dss/pkg/scd/models"
"github.com/interuss/stacktrace"
"github.com/interuss/dss/pkg/timestamp"
"github.com/jackc/pgx/v5"
)

func (rec *availabilityRecord) toModel() *scdmodels.UssAvailabilityStatus {
return &scdmodels.UssAvailabilityStatus{
Uss: rec.Uss,
Availability: rec.Availability,
Version: scdmodels.NewOVNFromTime(rec.UpdatedAt, rec.Uss.String()),
}
}

func (r *repo) GetUssAvailability(_ context.Context, id dssmodels.Manager) (*scdmodels.UssAvailabilityStatus, error) {
return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "GetUssAvailability not implemented for memstore")
rec, ok := r.state.Availabilities[id]
if !ok {
return nil, pgx.ErrNoRows // TODO: #1608
}
return rec.toModel(), nil
}

func (r *repo) UpsertUssAvailability(_ context.Context, ussa *scdmodels.UssAvailabilityStatus) (*scdmodels.UssAvailabilityStatus, error) {
return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "UpsertUssAvailability not implemented for memstore")
func (r *repo) UpsertUssAvailability(ctx context.Context, s *scdmodels.UssAvailabilityStatus) (*scdmodels.UssAvailabilityStatus, error) {
now := timestamp.MustGetRequestTimestamp(ctx)

rec := &availabilityRecord{
Uss: s.Uss,
Availability: s.Availability,
UpdatedAt: now,
}
r.state.Availabilities[s.Uss] = rec
return rec.toModel(), nil
}
31 changes: 31 additions & 0 deletions pkg/scd/store/memstore/availability_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package memstore

import (
"testing"

scdmodels "github.com/interuss/dss/pkg/scd/models"
"github.com/jackc/pgx/v5"
"github.com/stretchr/testify/require"
)

func TestUssAvailabilityUpsertGet(t *testing.T) {
ctx := writeCtx()
r := setUpStore(t)

got, err := r.UpsertUssAvailability(ctx, sampleAvailability())
require.NoError(t, err)
require.Equal(t, manager, got.Uss)
require.Equal(t, scdmodels.UssAvailabilityStateNormal, got.Availability)
require.Equal(t, scdmodels.OVN("HXjEfPAc0lkinCf0ejtSGiGPE4o2Qogm-iXAGPG-QNo_"), got.Version)

fetched, err := r.GetUssAvailability(ctx, manager)
Comment thread
mickmis marked this conversation as resolved.
require.NoError(t, err)
require.Equal(t, got.Version, fetched.Version)
require.Equal(t, scdmodels.UssAvailabilityStateNormal, fetched.Availability)
}

func TestGetUssAvailabilityMissingReturnsErrNoRows(t *testing.T) {
r := setUpStore(t)
_, err := r.GetUssAvailability(writeCtx(), manager)
require.ErrorIs(t, err, pgx.ErrNoRows)
}
82 changes: 75 additions & 7 deletions pkg/scd/store/memstore/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,97 @@ package memstore

import (
"context"
"slices"

dsserr "github.com/interuss/dss/pkg/errors"
"github.com/interuss/dss/pkg/memstore/utils"
dssmodels "github.com/interuss/dss/pkg/models"
scdmodels "github.com/interuss/dss/pkg/scd/models"
dsssql "github.com/interuss/dss/pkg/sql"
"github.com/interuss/dss/pkg/timestamp"
"github.com/interuss/stacktrace"
"github.com/jackc/pgx/v5"
)

func (rec *constraintRecord) toModel() *scdmodels.Constraint {
return &scdmodels.Constraint{
ID: rec.ID,
Manager: rec.Manager,
Version: rec.Version,
OVN: scdmodels.NewOVNFromTime(rec.UpdatedAt, rec.ID.String()),
StartTime: utils.ClonePtr(rec.StartTime),
EndTime: utils.ClonePtr(rec.EndTime),
USSBaseURL: rec.USSBaseURL,
AltitudeLower: utils.ClonePtr(rec.AltitudeLower),
AltitudeUpper: utils.ClonePtr(rec.AltitudeUpper),
Cells: slices.Clone(rec.Cells),
}
}

func (r *repo) SearchConstraints(_ context.Context, v4d *dssmodels.Volume4D) ([]*scdmodels.Constraint, error) {
return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "SearchConstraints not implemented for memstore")
want, err := coveringSet(v4d)
if err != nil {
return nil, err
}
if len(want) == 0 {
return []*scdmodels.Constraint{}, nil
}

var out []*scdmodels.Constraint
for _, rec := range r.state.Constraints {
if !overlaps(rec.Cells, want) {
continue
}
if !overlapsTime(rec.StartTime, rec.EndTime, v4d) {
continue
}
out = append(out, rec.toModel())

if len(out) >= dssmodels.MaxResultLimit { // mirror SQL "LIMIT MaxResultLimit"
break
}
}
return out, nil
}

func (r *repo) GetConstraint(_ context.Context, id dssmodels.ID) (*scdmodels.Constraint, error) {
return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "GetConstraint not implemented for memstore")
rec, ok := r.state.Constraints[id]
if !ok {
return nil, pgx.ErrNoRows // TODO: #1608
}
return rec.toModel(), nil
}

func (r *repo) UpsertConstraint(_ context.Context, constraint *scdmodels.Constraint) (*scdmodels.Constraint, error) {
return nil, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "UpsertConstraint not implemented for memstore")
func (r *repo) UpsertConstraint(ctx context.Context, s *scdmodels.Constraint) (*scdmodels.Constraint, error) {
if _, err := dsssql.CellUnionToCellIdsWithValidation(s.Cells); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validate with geo.ValidateCell then?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a small helper on the loop + convert, is it worth it to change it back?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd factor away the loop in utils.go and use it here. But is it worth it? Meh up to you.

return nil, stacktrace.Propagate(err, "Failed to convert array to jackc/pgtype")
}

now := timestamp.MustGetRequestTimestamp(ctx)

rec := &constraintRecord{
ID: s.ID,
Manager: s.Manager,
Version: s.Version,
StartTime: utils.ClonePtr(s.StartTime),
EndTime: utils.ClonePtr(s.EndTime),
USSBaseURL: s.USSBaseURL,
AltitudeLower: utils.ClonePtr(s.AltitudeLower),
AltitudeUpper: utils.ClonePtr(s.AltitudeUpper),
Cells: slices.Clone(s.Cells),
UpdatedAt: now,
}
r.state.Constraints[s.ID] = rec
return rec.toModel(), nil
}

func (r *repo) DeleteConstraint(_ context.Context, id dssmodels.ID) error {
return stacktrace.NewErrorWithCode(dsserr.NotImplemented, "DeleteConstraint not implemented for memstore")
if _, ok := r.state.Constraints[id]; !ok {
return pgx.ErrNoRows // TODO: #1608
}
delete(r.state.Constraints, id)
return nil
}

func (r *repo) CountConstraints(_ context.Context) (int64, error) {
return 0, stacktrace.NewErrorWithCode(dsserr.NotImplemented, "CountConstraint not implemented for memstore")
return int64(len(r.state.Constraints)), nil
}
73 changes: 73 additions & 0 deletions pkg/scd/store/memstore/constraints_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package memstore

import (
"testing"
"time"

"github.com/golang/geo/s2"
"github.com/interuss/dss/pkg/scd/models"
"github.com/jackc/pgx/v5"
"github.com/stretchr/testify/require"
)

func TestConstraintUpsertGetDelete(t *testing.T) {
ctx := writeCtx()
r := setUpStore(t)

got, err := r.UpsertConstraint(ctx, sampleConstraint())
require.NoError(t, err)
require.Equal(t, constraintId, got.ID)
require.Equal(t, manager, got.Manager)
require.Equal(t, models.OVN("4Ne9uzrR5K9LYEyJ-c6rTI0r-FTuLQGMSBR1j.SaTvk_"), got.OVN)

fetched, err := r.GetConstraint(ctx, constraintId)
require.NoError(t, err)
require.Equal(t, got.OVN, fetched.OVN)
require.Equal(t, cells, fetched.Cells)

count, err := r.CountConstraints(ctx)
require.NoError(t, err)
require.Equal(t, int64(1), count)

require.NoError(t, r.DeleteConstraint(ctx, constraintId))

_, err = r.GetConstraint(ctx, constraintId)
require.ErrorIs(t, err, pgx.ErrNoRows)
}

func TestConstraintGetMissingReturnsErrNoRows(t *testing.T) {
r := setUpStore(t)
_, err := r.GetConstraint(writeCtx(), constraintId)
require.ErrorIs(t, err, pgx.ErrNoRows)
}

func TestConstraintDeleteMissingReturnsErrNoRows(t *testing.T) {
r := setUpStore(t)
err := r.DeleteConstraint(writeCtx(), constraintId)
require.ErrorIs(t, err, pgx.ErrNoRows)
}

func TestSearchConstraints(t *testing.T) {
ctx := writeCtx()
r := setUpStore(t)
_, err := r.UpsertConstraint(ctx, sampleConstraint())
require.NoError(t, err)

// Overlapping volume with no time bounds matches.
res, err := r.SearchConstraints(ctx, volume4D(cells, nil, nil, nil, nil))
require.NoError(t, err)
require.Len(t, res, 1)

// Time window after the constraint's end excludes it.
afterStart := endTime.Add(time.Hour)
afterEnd := afterStart.Add(time.Hour)
res, err = r.SearchConstraints(ctx, volume4D(cells, &afterStart, &afterEnd, nil, nil))
require.NoError(t, err)
require.Empty(t, res)

// No covering cells returns an empty (non-nil) slice.
res, err = r.SearchConstraints(ctx, volume4D(s2.CellUnion{}, nil, nil, nil, nil))
require.NoError(t, err)
require.NotNil(t, res)
require.Empty(t, res)
}
Loading
Loading