mirror of https://github.com/hashicorp/boundary
parent
f8b2e99e11
commit
edae7c899f
@ -0,0 +1,92 @@
|
||||
begin;
|
||||
|
||||
-- Remove invalid (states with no prior end time) rows
|
||||
delete from session_state where previous_end_time is null and state != 'pending';
|
||||
|
||||
-- In the case of dupes that are otherwise valid, identify first valid session state and remove others
|
||||
with cte_session_state_delete
|
||||
as (select session_id, min(start_time) as st_t, state from session_state ou
|
||||
where (select count(*) from session_state inr
|
||||
where inr.session_id = ou.session_id and inr.state = ou.state) > 1
|
||||
group by session_id, state)
|
||||
delete from session_state t1
|
||||
using cte_session_state_delete t2 where t1.session_id =t2.session_id and t1.start_time > t2.st_t and t1.state=t2.state;
|
||||
|
||||
-- Close all open session_connections
|
||||
update session_connection
|
||||
set closed_reason='canceled' where closed_reason is null;
|
||||
|
||||
update session
|
||||
set termination_reason='canceled' where termination_reason is null;
|
||||
|
||||
-- End of data cleanup; start of table modifications
|
||||
|
||||
-- session_valid_state table creation and related constraints
|
||||
create table session_valid_state(
|
||||
prior_state text
|
||||
references session_state_enm(name)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
constraint prior_state_session_state_enm_fkey
|
||||
check (
|
||||
prior_state in ('pending', 'active', 'canceling')
|
||||
),
|
||||
current_state text
|
||||
references session_state_enm(name)
|
||||
on delete restrict
|
||||
on update cascade,
|
||||
constraint current_state_session_state_enm_fkey
|
||||
check (
|
||||
current_state in ('pending', 'active', 'canceling', 'terminated')
|
||||
),
|
||||
primary key (prior_state, current_state)
|
||||
);
|
||||
comment on table session_valid_state is
|
||||
'session_valid_state entries define valid prior_state and current_state pairs to define valid state transitions';
|
||||
|
||||
insert into session_valid_state (prior_state, current_state)
|
||||
values
|
||||
('pending','pending'),
|
||||
('pending','active'),
|
||||
('pending','terminated'),
|
||||
('pending','canceling'),
|
||||
('active','canceling'),
|
||||
('active','terminated'),
|
||||
('canceling','terminated');
|
||||
|
||||
alter table session_state
|
||||
add column prior_state text not null default 'pending'
|
||||
references session_state_enm(name)
|
||||
on delete restrict
|
||||
on update cascade;
|
||||
alter table session_state
|
||||
add constraint session_valid_state_enm_fkey
|
||||
foreign key (prior_state, state)
|
||||
references session_valid_state (prior_state,current_state);
|
||||
alter table session_state
|
||||
add unique (session_id, state);
|
||||
|
||||
create function
|
||||
update_prior_session_state()
|
||||
returns trigger
|
||||
as $$
|
||||
begin
|
||||
-- Prior state is the most recent valid prior state entry for this session_id
|
||||
new.prior_state = query.state from(
|
||||
select state from session_state where session_id=new.session_id and state in(
|
||||
select prior_state from session_valid_state where current_state=new.state )
|
||||
order by start_time desc limit 1) as query;
|
||||
|
||||
if new.prior_state is null then
|
||||
new.prior_state='pending';
|
||||
end if;
|
||||
|
||||
return new;
|
||||
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger update_session_state before insert on session_state
|
||||
for each row execute procedure update_prior_session_state();
|
||||
|
||||
commit;
|
||||
@ -0,0 +1,165 @@
|
||||
package oss_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/host/static"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/session"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/target/tcp"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrations_SessionState(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
const (
|
||||
priorMigration = 21002
|
||||
currentMigration = 24001
|
||||
)
|
||||
dialect := dbtest.Postgres
|
||||
ctx := context.Background()
|
||||
|
||||
c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
|
||||
require.NoError(err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(c())
|
||||
})
|
||||
d, err := common.SqlOpen(dialect, u)
|
||||
require.NoError(err)
|
||||
|
||||
// migration to the prior migration (before the one we want to test)
|
||||
m, err := schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err := m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want := &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: priorMigration,
|
||||
DatabaseSchemaVersion: priorMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(want, state)
|
||||
|
||||
// Seed the database with test data
|
||||
dbType, err := db.StringToDbType(dialect)
|
||||
require.NoError(err)
|
||||
|
||||
conn, err := db.Open(dbType, u)
|
||||
require.NoError(err)
|
||||
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
require.NotNil(prj)
|
||||
|
||||
hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0]
|
||||
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
|
||||
h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0]
|
||||
static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h})
|
||||
|
||||
tar := tcp.TestTarget(ctx, t, conn, prj.GetPublicId(), "test", target.WithHostSources([]string{hs.GetPublicId()}))
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
{
|
||||
at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId())
|
||||
uId := at.GetIamUserId()
|
||||
|
||||
sess := session.TestSession(t, conn, wrapper, session.ComposedOf{
|
||||
UserId: uId,
|
||||
HostId: h.GetPublicId(),
|
||||
TargetId: tar.GetPublicId(),
|
||||
HostSetId: hs.GetPublicId(),
|
||||
AuthTokenId: at.GetPublicId(),
|
||||
ScopeId: prj.GetPublicId(),
|
||||
Endpoint: "tcp://127.0.0.1:22",
|
||||
ConnectionLimit: -1,
|
||||
})
|
||||
|
||||
session.TestConnection(t, conn, sess.PublicId, "127.0.0.1", 22,
|
||||
"127.0.0.2", 23, "127.0.0.1")
|
||||
}
|
||||
|
||||
// now we're ready for the migration we want to test.
|
||||
m, err = schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": currentMigration}),
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err = m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want = &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: currentMigration,
|
||||
DatabaseSchemaVersion: currentMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(want, state)
|
||||
|
||||
sessionRepo, err := session.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
// Ensure session is cancelled
|
||||
repoSessions, err := sessionRepo.ListSessions(ctx)
|
||||
require.NoError(err)
|
||||
var sessionTermReason []string
|
||||
var repoSessionId string
|
||||
for i := 0; i < len(repoSessions); i++ {
|
||||
s := repoSessions[i]
|
||||
repoSessionId = s.PublicId
|
||||
sessionTermReason = append(sessionTermReason, s.TerminationReason)
|
||||
}
|
||||
require.Equal([]string{"canceled"}, sessionTermReason)
|
||||
|
||||
// Ensure connection is also cancelled
|
||||
connections, err := sessionRepo.ListConnectionsBySessionId(ctx, repoSessionId)
|
||||
require.NoError(err)
|
||||
|
||||
var connTermReason []string
|
||||
for i := 0; i < len(connections); i++ {
|
||||
c := connections[i]
|
||||
connTermReason = append(connTermReason, c.ClosedReason)
|
||||
}
|
||||
require.Equal([]string{"canceled"}, connTermReason)
|
||||
|
||||
// Validate new table contents
|
||||
rows, err := d.QueryContext(ctx, "select * from session_valid_state")
|
||||
require.NoError(err)
|
||||
|
||||
var validStates []string
|
||||
for rows.Next() {
|
||||
var a, b string
|
||||
require.NoError(rows.Scan(&a, &b))
|
||||
validStates = append(validStates, []string{a, b}...)
|
||||
}
|
||||
require.Equal([]string{
|
||||
"pending", "pending", "pending", "active", "pending", "terminated",
|
||||
"pending", "canceling", "active", "canceling", "active", "terminated", "canceling", "terminated",
|
||||
}, validStates)
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
begin;
|
||||
|
||||
-- For use in testing session_state transitions
|
||||
insert into session_state
|
||||
(session_id, state)
|
||||
values
|
||||
('s1_____cindy','terminated'),
|
||||
('s1_____ciara','canceling'),
|
||||
('s1_____carly','active');
|
||||
|
||||
commit;
|
||||
@ -0,0 +1,43 @@
|
||||
begin;
|
||||
select plan(12);
|
||||
|
||||
-- Ensure session state table is populated
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='pending';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____cindy' and state='terminated';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____ciara' and state='canceling';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____carly' and state='active';
|
||||
|
||||
-- Invalid duplicate state
|
||||
select throws_ok($$ insert into session_state ( session_id, state )
|
||||
values ('s1_____clare','pending')$$);
|
||||
|
||||
-- Invalid state transition from terminated to pending
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______cindy','pending')$$);
|
||||
|
||||
-- Invalid state transition from terminated to active
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______cindy','active')$$);
|
||||
|
||||
-- Invalid state transition from terminated to canceling
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______cindy','canceling')$$);
|
||||
|
||||
-- Invalid state transition from terminated to terminated
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______cindy','terminated')$$);
|
||||
|
||||
-- Invalid state transition from active to pending
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______carly','pending')$$);
|
||||
|
||||
-- Invalid state transition from canceling to pending
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______ciara','pending')$$);
|
||||
|
||||
-- Invalid state transition from canceling to active
|
||||
select throws_ok($$ insert into session_state ( session_id, state)
|
||||
values ('s1______ciara','active')$$);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@ -0,0 +1,28 @@
|
||||
begin;
|
||||
select plan(7);
|
||||
|
||||
-- Ensure session state table is populated
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='pending';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____carly' and state='active';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____ciara' and state='canceling';
|
||||
|
||||
-- Valid state transition from pending to canceling
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____clare','canceling');
|
||||
select is(count(*), 2::bigint) from session_state where session_id = 's1_____clare';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='canceling';
|
||||
|
||||
|
||||
-- Valid state transition from active to canceling
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____carly','canceling');
|
||||
select is(count(*), 3::bigint) from session_state where session_id = 's1_____carly';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____carly' and state='canceling';
|
||||
|
||||
select * from finish();
|
||||
|
||||
rollback;
|
||||
@ -0,0 +1,17 @@
|
||||
begin;
|
||||
select plan(3);
|
||||
|
||||
-- Ensure session state table is populated
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='pending';
|
||||
|
||||
-- Valid state transition from pending to active
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____clare','active');
|
||||
|
||||
select is(count(*), 2::bigint) from session_state where session_id = 's1_____clare';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='active';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@ -0,0 +1,17 @@
|
||||
begin;
|
||||
select plan(3);
|
||||
|
||||
-- Ensure session state table is populated
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='pending';
|
||||
|
||||
-- Valid state transition from pending to canceling
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____clare','canceling');
|
||||
|
||||
select is(count(*), 2::bigint) from session_state where session_id = 's1_____clare';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='canceling';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
@ -0,0 +1,35 @@
|
||||
begin;
|
||||
select plan(9);
|
||||
|
||||
-- Ensure session state table is populated
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='pending';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____carly' and state='active';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____ciara' and state='canceling';
|
||||
|
||||
-- Valid state transition from pending to terminated
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____clare','terminated');
|
||||
select is(count(*), 2::bigint) from session_state where session_id = 's1_____clare';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____clare' and state='terminated';
|
||||
|
||||
|
||||
-- Valid state transition from active to terminated
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____carly','terminated');
|
||||
select is(count(*), 3::bigint) from session_state where session_id = 's1_____carly';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____carly' and state='terminated';
|
||||
|
||||
-- Valid state transition from canceling to terminated
|
||||
insert into session_state
|
||||
( session_id, state)
|
||||
values
|
||||
('s1_____ciara','terminated');
|
||||
select is(count(*), 3::bigint) from session_state where session_id = 's1_____ciara';
|
||||
select is(count(*), 1::bigint) from session_state where session_id = 's1_____ciara' and state='terminated';
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
Loading…
Reference in new issue