Skip to content

fix(cli): port-forward: update workspace last_used_at #12659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Mar 20, 2024
Merged
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
279f874
add tests to assert last used at updated on port-forward
johnstcn Mar 18, 2024
8f9b945
add workspaceusage package
johnstcn Mar 18, 2024
e8c842c
add workspace usager tracking to coderd, add endpoint
johnstcn Mar 18, 2024
86704a1
add workspace usage tracking to cli/portforward, fix tests
johnstcn Mar 19, 2024
c99327c
make gen
johnstcn Mar 19, 2024
5876edd
workspaceusage: improve locking and tests
johnstcn Mar 19, 2024
e4e0311
address more PR comments
johnstcn Mar 19, 2024
958d1d1
try to race harder
johnstcn Mar 19, 2024
a36aeb9
add danny's suggestions
johnstcn Mar 20, 2024
692f666
add big big comments
johnstcn Mar 20, 2024
d794e00
fix(database): BatchUpdateWorkspaceLastUsedAt: avoid overwriting olde…
johnstcn Mar 20, 2024
45a0eef
fix(coderd/workspaceusage): log number of consecutive flush errors
johnstcn Mar 20, 2024
8e40efd
upgrade to error log on multiple flush failures
johnstcn Mar 20, 2024
591e1ab
chore(coderd/workspaceusage): add integration-style test with multipl…
johnstcn Mar 20, 2024
0caaf3a
fix(cli/portforward_test.go): use testutil.RequireRecv/SendCtx
johnstcn Mar 20, 2024
cc72868
just use default flush interval
johnstcn Mar 20, 2024
f5f8d75
rename receiver
johnstcn Mar 20, 2024
a2e716d
defer close doneCh
johnstcn Mar 20, 2024
5b64f96
defer instead of cleanup, avoid data race in real pubsub
johnstcn Mar 20, 2024
23ccf21
fix(coderdtest): buffer just in case
johnstcn Mar 20, 2024
c9ac9d2
refactor: unexport Loop, remove panic, simplify external API
johnstcn Mar 20, 2024
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
Prev Previous commit
Next Next commit
chore(coderd/workspaceusage): add integration-style test with multipl…
…e instances
  • Loading branch information
johnstcn committed Mar 20, 2024
commit 591e1abffe1f47651ff6fcd1a204575ddacd4498
113 changes: 113 additions & 0 deletions coderd/workspaceusage/tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ import (

"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbfake"
"github.com/coder/coder/v2/coderd/database/dbmock"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/workspaceusage"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)

func TestTracker(t *testing.T) {
Expand Down Expand Up @@ -105,6 +110,114 @@ func TestTracker(t *testing.T) {
require.Panics(t, wut.Loop)
}

// This test performs a more 'integration-style' test with multiple instances.
func TestTracker_MultipleInstances(t *testing.T) {
t.Parallel()
if !dbtestutil.WillUsePostgres() {
t.Skip("this test only makes sense with postgres")
}

// Given we have two coderd instances connected to the same database
var (
ctx = testutil.Context(t, testutil.WaitLong)
db, ps = dbtestutil.NewDB(t)
wuTickA = make(chan time.Time)
wuFlushA = make(chan int, 1)
wutA = workspaceusage.New(db, workspaceusage.WithFlushChannel(wuFlushA), workspaceusage.WithTickChannel(wuTickA))
wuTickB = make(chan time.Time)
wuFlushB = make(chan int, 1)
wutB = workspaceusage.New(db, workspaceusage.WithFlushChannel(wuFlushB), workspaceusage.WithTickChannel(wuTickB))
clientA = coderdtest.New(t, &coderdtest.Options{
WorkspaceUsageTracker: wutA,
Database: db,
Pubsub: ps,
})
clientB = coderdtest.New(t, &coderdtest.Options{
WorkspaceUsageTracker: wutB,
Database: db,
Pubsub: ps,
})
owner = coderdtest.CreateFirstUser(t, clientA)
now = dbtime.Now()
)

clientB.SetSessionToken(clientA.SessionToken())

// Create a number of workspaces
numWorkspaces := 10
w := make([]dbfake.WorkspaceResponse, numWorkspaces)
for i := 0; i < numWorkspaces; i++ {
wr := dbfake.WorkspaceBuild(t, db, database.Workspace{
OwnerID: owner.UserID,
OrganizationID: owner.OrganizationID,
LastUsedAt: now,
}).WithAgent().Do()
w[i] = wr
}

// Use client A to update LastUsedAt of the first three
require.NoError(t, clientA.PostWorkspaceUsage(ctx, w[0].Workspace.ID))
require.NoError(t, clientA.PostWorkspaceUsage(ctx, w[1].Workspace.ID))
require.NoError(t, clientA.PostWorkspaceUsage(ctx, w[2].Workspace.ID))
// Use client B to update LastUsedAt of the next three
require.NoError(t, clientB.PostWorkspaceUsage(ctx, w[3].Workspace.ID))
require.NoError(t, clientB.PostWorkspaceUsage(ctx, w[4].Workspace.ID))
require.NoError(t, clientB.PostWorkspaceUsage(ctx, w[5].Workspace.ID))
// The next two will have updated from both instances
require.NoError(t, clientA.PostWorkspaceUsage(ctx, w[6].Workspace.ID))
require.NoError(t, clientB.PostWorkspaceUsage(ctx, w[6].Workspace.ID))
require.NoError(t, clientA.PostWorkspaceUsage(ctx, w[7].Workspace.ID))
require.NoError(t, clientB.PostWorkspaceUsage(ctx, w[7].Workspace.ID))
// The last two will not report any usage.

// Tick both with different times and wait for both flushes to complete
nowA := now.Add(time.Minute)
nowB := now.Add(2 * time.Minute)
var wg sync.WaitGroup
var flushedA, flushedB int
wg.Add(1)
go func() {
defer wg.Done()
wuTickA <- nowA
flushedA = <-wuFlushA
}()
wg.Add(1)
go func() {
defer wg.Done()
wuTickB <- nowB
flushedB = <-wuFlushB
}()
wg.Wait()

// We expect 5 flushed IDs each
require.Equal(t, 5, flushedA)
require.Equal(t, 5, flushedB)

// Fetch updated workspaces
updated := make([]codersdk.Workspace, numWorkspaces)
for i := 0; i < numWorkspaces; i++ {
ws, err := clientA.Workspace(ctx, w[i].Workspace.ID)
require.NoError(t, err)
updated[i] = ws
}
// We expect the first three to have the timestamp of flushA
require.Equal(t, nowA.UTC(), updated[0].LastUsedAt.UTC())
require.Equal(t, nowA.UTC(), updated[1].LastUsedAt.UTC())
require.Equal(t, nowA.UTC(), updated[2].LastUsedAt.UTC())
// We expect the next three to have the timestamp of flushB
require.Equal(t, nowB.UTC(), updated[3].LastUsedAt.UTC())
require.Equal(t, nowB.UTC(), updated[4].LastUsedAt.UTC())
require.Equal(t, nowB.UTC(), updated[5].LastUsedAt.UTC())
// The next two should have the timestamp of flushB as it is newer than flushA
require.Equal(t, nowB.UTC(), updated[6].LastUsedAt.UTC())
require.Equal(t, nowB.UTC(), updated[7].LastUsedAt.UTC())
// And the last two should be untouched
require.Equal(t, w[8].Workspace.LastUsedAt.UTC(), updated[8].LastUsedAt.UTC())
require.Equal(t, w[8].Workspace.LastUsedAt.UTC(), updated[8].LastUsedAt.UTC())
require.Equal(t, w[9].Workspace.LastUsedAt.UTC(), updated[9].LastUsedAt.UTC())
require.Equal(t, w[9].Workspace.LastUsedAt.UTC(), updated[9].LastUsedAt.UTC())
}

func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}