Skip to content
Open
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
6 changes: 5 additions & 1 deletion server/internal/api/apiv1/post_init_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,11 @@ func (s *PostInitHandlers) DeleteDatabase(ctx context.Context, req *api.DeleteDa
return nil, ErrDatabaseNotModifiable
}

prevState := db.State
// Use the raw stored state as the guard for this transition.
prevState, err := s.dbSvc.GetStoredDatabaseState(ctx, db.DatabaseID)

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.

Looks good.
Minor: DeleteDatabase now calls GetStoredDatabaseState (post_init_handlers.go:479), which re-reads the same StoredDatabase record already fetched by GetDatabase just above. We could avoid the extra database round trip by carrying the raw stored state on Database (for example, a RawState field populated in storedToDatabase) and using db.RawState here instead. Not blocking.

if err != nil {
return nil, apiErr(err)
}
err = s.dbSvc.UpdateDatabaseState(ctx, db.DatabaseID, prevState, database.DatabaseStateDeleting)
if err != nil {
return nil, apiErr(err)
Expand Down
21 changes: 19 additions & 2 deletions server/internal/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,31 @@ func databaseToStored(d *Database) *StoredDatabase {
}
}

var degradedInstanceStates = ds.NewSet(
InstanceStateDegraded,
InstanceStateFailed,
InstanceStateUnknown,
InstanceStateStopped,
)

func storedToDatabase(d *StoredDatabase, storedSpec *StoredSpec, instances []*Instance, serviceInstances []*ServiceInstance) *Database {
state := d.State

if state == DatabaseStateAvailable {
for _, instance := range instances {
if degradedInstanceStates.Has(instance.State) {
state = DatabaseStateDegraded
break
}
}
}

return &Database{
DatabaseID: d.DatabaseID,
TenantID: d.TenantID,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
State: d.State,
State: state,
Spec: storedSpec.Spec,
Instances: instances,
ServiceInstances: serviceInstances,
Expand All @@ -95,7 +113,6 @@ func storedToDatabases(storedDbs []*StoredDatabase, storedSpecs []*StoredSpec, a
for _, instance := range allInstances {
instancesByID[instance.DatabaseID] = append(instancesByID[instance.DatabaseID], instance)
}

serviceInstancesByID := make(map[string][]*ServiceInstance, len(allServiceInstances))
for _, serviceInstance := range allServiceInstances {
serviceInstancesByID[serviceInstance.DatabaseID] = append(serviceInstancesByID[serviceInstance.DatabaseID], serviceInstance)
Expand Down
54 changes: 54 additions & 0 deletions server/internal/database/database_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package database

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestStoredToDatabaseDegradesWhenInstanceUnavailable(t *testing.T) {
tests := []struct {
name string
instanceState InstanceState
wantState DatabaseState
}{
{"available instance keeps database available", InstanceStateAvailable, DatabaseStateAvailable},
{"degraded instance degrades database", InstanceStateDegraded, DatabaseStateDegraded},
{"failed instance degrades database", InstanceStateFailed, DatabaseStateDegraded},
{"unknown instance degrades database", InstanceStateUnknown, DatabaseStateDegraded},
{"stopped instance degrades database", InstanceStateStopped, DatabaseStateDegraded},
{"creating instance does not degrade database", InstanceStateCreating, DatabaseStateAvailable},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stored := &StoredDatabase{
DatabaseID: "db1",
State: DatabaseStateAvailable,
}
storedSpec := &StoredSpec{}
instances := []*Instance{
{InstanceID: "i1", DatabaseID: "db1", State: tt.instanceState},
}

db := storedToDatabase(stored, storedSpec, instances, nil)

assert.Equal(t, tt.wantState, db.State)
})
}
}

func TestStoredToDatabaseDoesNotDegradeNonAvailableDatabase(t *testing.T) {
stored := &StoredDatabase{
DatabaseID: "db1",
State: DatabaseStateCreating,
}
storedSpec := &StoredSpec{}
instances := []*Instance{
{InstanceID: "i1", DatabaseID: "db1", State: InstanceStateFailed},
}

db := storedToDatabase(stored, storedSpec, instances, nil)

assert.Equal(t, DatabaseStateCreating, db.State)
}