mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
Currently, importing `codersdk` just to interact with the API requires importing tailscale, which causes builds to fail unless manually using our fork.
223 lines
7.3 KiB
Go
223 lines
7.3 KiB
Go
package coderd_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog/sloggers/slogtest"
|
|
"github.com/coder/coder/v2/agent/agenttest"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/coderd/schedule"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
|
"github.com/coder/coder/v2/provisioner/echo"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestWorkspaceActivityBump(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
|
|
// deadline allows you to forcibly set a max_deadline on the build. This
|
|
// doesn't use template autostop requirements and instead edits the
|
|
// max_deadline on the build directly in the database.
|
|
setupActivityTest := func(t *testing.T, deadline ...time.Duration) (client *codersdk.Client, workspace codersdk.Workspace, assertBumped func(want bool)) {
|
|
t.Helper()
|
|
const ttl = time.Hour
|
|
|
|
db, pubsub := dbtestutil.NewDB(t)
|
|
client = coderdtest.New(t, &coderdtest.Options{
|
|
Database: db,
|
|
Pubsub: pubsub,
|
|
IncludeProvisionerDaemon: true,
|
|
// Agent stats trigger the activity bump, so we want to report
|
|
// very frequently in tests.
|
|
AgentStatsRefreshInterval: time.Millisecond * 100,
|
|
TemplateScheduleStore: schedule.MockTemplateScheduleStore{
|
|
GetFn: func(ctx context.Context, db database.Store, templateID uuid.UUID) (schedule.TemplateScheduleOptions, error) {
|
|
return schedule.TemplateScheduleOptions{
|
|
UserAutostopEnabled: true,
|
|
DefaultTTL: ttl,
|
|
// We set max_deadline manually below.
|
|
AutostopRequirement: schedule.TemplateAutostopRequirement{},
|
|
}, nil
|
|
},
|
|
},
|
|
})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
ttlMillis := int64(ttl / time.Millisecond)
|
|
agentToken := uuid.NewString()
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
|
Parse: echo.ParseComplete,
|
|
ProvisionPlan: echo.PlanComplete,
|
|
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
|
|
})
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
|
cwr.TTLMillis = &ttlMillis
|
|
})
|
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
|
|
|
var maxDeadline time.Time
|
|
// Update the max deadline.
|
|
if len(deadline) > 0 {
|
|
maxDeadline = dbtime.Now().Add(deadline[0])
|
|
}
|
|
|
|
err := db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
|
ID: workspace.LatestBuild.ID,
|
|
UpdatedAt: dbtime.Now(),
|
|
// Make the deadline really close so it needs to be bumped immediately.
|
|
Deadline: dbtime.Now().Add(time.Minute),
|
|
MaxDeadline: maxDeadline,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_ = agenttest.New(t, client.URL, agentToken)
|
|
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
|
|
// Sanity-check that deadline is nearing requiring a bump.
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
|
require.NoError(t, err)
|
|
require.WithinDuration(t,
|
|
time.Now().Add(time.Minute),
|
|
workspace.LatestBuild.Deadline.Time,
|
|
testutil.WaitMedium,
|
|
)
|
|
firstDeadline := workspace.LatestBuild.Deadline.Time
|
|
|
|
if !maxDeadline.IsZero() {
|
|
require.WithinDuration(t,
|
|
maxDeadline,
|
|
workspace.LatestBuild.MaxDeadline.Time,
|
|
testutil.WaitMedium,
|
|
)
|
|
} else {
|
|
require.True(t, workspace.LatestBuild.MaxDeadline.Time.IsZero())
|
|
}
|
|
|
|
_ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
|
|
return client, workspace, func(want bool) {
|
|
t.Helper()
|
|
if !want {
|
|
// It is difficult to test the absence of a call in a non-racey
|
|
// way. In general, it is difficult for the API to generate
|
|
// false positive activity since Agent networking event
|
|
// is required. The Activity Bump behavior is also coupled with
|
|
// Last Used, so it would be obvious to the user if we
|
|
// are falsely recognizing activity.
|
|
time.Sleep(testutil.IntervalMedium)
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, workspace.LatestBuild.Deadline.Time, firstDeadline)
|
|
return
|
|
}
|
|
|
|
var updatedAfter time.Time
|
|
// The Deadline bump occurs asynchronously.
|
|
require.Eventuallyf(t,
|
|
func() bool {
|
|
workspace, err = client.Workspace(ctx, workspace.ID)
|
|
require.NoError(t, err)
|
|
updatedAfter = dbtime.Now()
|
|
if workspace.LatestBuild.Deadline.Time.Equal(firstDeadline) {
|
|
updatedAfter = time.Now()
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
testutil.WaitLong, testutil.IntervalFast,
|
|
"deadline %v never updated", firstDeadline,
|
|
)
|
|
|
|
require.Greater(t, workspace.LatestBuild.Deadline.Time, updatedAfter)
|
|
|
|
// If the workspace has a max deadline, the deadline must not exceed
|
|
// it.
|
|
if workspace.LatestBuild.MaxDeadline.Valid {
|
|
require.LessOrEqual(t, workspace.LatestBuild.Deadline.Time, workspace.LatestBuild.MaxDeadline.Time)
|
|
return
|
|
}
|
|
now := dbtime.Now()
|
|
zone, offset := time.Now().Zone()
|
|
t.Logf("[Zone=%s %d] originDeadline: %s, deadline: %s, now %s, (now-deadline)=%s",
|
|
zone, offset,
|
|
firstDeadline, workspace.LatestBuild.Deadline.Time, now,
|
|
now.Sub(workspace.LatestBuild.Deadline.Time),
|
|
)
|
|
require.WithinDuration(t, dbtime.Now().Add(ttl), workspace.LatestBuild.Deadline.Time, testutil.WaitShort)
|
|
}
|
|
}
|
|
|
|
t.Run("Dial", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, workspace, assertBumped := setupActivityTest(t)
|
|
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
conn, err := workspacesdk.New(client).
|
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
|
Logger: slogtest.Make(t, nil),
|
|
})
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
|
|
// Must send network traffic after a few seconds to surpass bump threshold.
|
|
time.Sleep(time.Second * 3)
|
|
sshConn, err := conn.SSHClient(ctx)
|
|
require.NoError(t, err)
|
|
_ = sshConn.Close()
|
|
|
|
assertBumped(true)
|
|
})
|
|
|
|
t.Run("NoBump", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, workspace, assertBumped := setupActivityTest(t)
|
|
|
|
// Benign operations like retrieving workspace must not
|
|
// bump the deadline.
|
|
_, err := client.Workspace(ctx, workspace.ID)
|
|
require.NoError(t, err)
|
|
|
|
assertBumped(false)
|
|
})
|
|
|
|
t.Run("NotExceedMaxDeadline", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Set the max deadline to be in 30min. We bump by 1 hour, so we
|
|
// should expect the deadline to match the max deadline exactly.
|
|
client, workspace, assertBumped := setupActivityTest(t, time.Minute*30)
|
|
|
|
// Bump by dialing the workspace and sending traffic.
|
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
|
conn, err := workspacesdk.New(client).
|
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
|
Logger: slogtest.Make(t, nil),
|
|
})
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
|
|
// Must send network traffic after a few seconds to surpass bump threshold.
|
|
time.Sleep(time.Second * 3)
|
|
sshConn, err := conn.SSHClient(ctx)
|
|
require.NoError(t, err)
|
|
_ = sshConn.Close()
|
|
|
|
assertBumped(true)
|
|
})
|
|
}
|