mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
@@ -3040,6 +3040,62 @@ func TestAgent_Reconnect(t *testing.T) {
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func TestAgent_ReconnectNoLifecycleReemit(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
logger := testutil.Logger(t)
|
||||
|
||||
fCoordinator := tailnettest.NewFakeCoordinator()
|
||||
agentID := uuid.New()
|
||||
statsCh := make(chan *proto.Stats, 50)
|
||||
derpMap, _ := tailnettest.RunDERPAndSTUN(t)
|
||||
|
||||
client := agenttest.NewClient(t,
|
||||
logger,
|
||||
agentID,
|
||||
agentsdk.Manifest{
|
||||
DERPMap: derpMap,
|
||||
Scripts: []codersdk.WorkspaceAgentScript{{
|
||||
Script: "echo hello",
|
||||
Timeout: 30 * time.Second,
|
||||
RunOnStart: true,
|
||||
}},
|
||||
},
|
||||
statsCh,
|
||||
fCoordinator,
|
||||
)
|
||||
defer client.Close()
|
||||
|
||||
closer := agent.New(agent.Options{
|
||||
Client: client,
|
||||
Logger: logger.Named("agent"),
|
||||
})
|
||||
defer closer.Close()
|
||||
|
||||
// Wait for the agent to reach Ready state.
|
||||
require.Eventually(t, func() bool {
|
||||
return slices.Contains(client.GetLifecycleStates(), codersdk.WorkspaceAgentLifecycleReady)
|
||||
}, testutil.WaitShort, testutil.IntervalFast)
|
||||
|
||||
statesBefore := slices.Clone(client.GetLifecycleStates())
|
||||
|
||||
// Disconnect by closing the coordinator response channel.
|
||||
call1 := testutil.RequireReceive(ctx, t, fCoordinator.CoordinateCalls)
|
||||
close(call1.Resps)
|
||||
|
||||
// Wait for reconnect.
|
||||
testutil.RequireReceive(ctx, t, fCoordinator.CoordinateCalls)
|
||||
|
||||
// Wait for a stats report as a deterministic steady-state proof.
|
||||
testutil.RequireReceive(ctx, t, statsCh)
|
||||
|
||||
statesAfter := client.GetLifecycleStates()
|
||||
require.Equal(t, statesBefore, statesAfter,
|
||||
"lifecycle states should not be re-reported after reconnect")
|
||||
|
||||
closer.Close()
|
||||
}
|
||||
|
||||
func TestAgent_WriteVSCodeConfigs(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := testutil.Logger(t)
|
||||
|
||||
@@ -8742,3 +8742,123 @@ func TestInsertWorkspaceAgentDevcontainers(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWorkspaceBuildMetricsByResourceID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
tmpl := dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: uuid.NullUUID{UUID: tmpl.ID, Valid: true},
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
ws := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: tmpl.ID,
|
||||
OwnerID: user.ID,
|
||||
AutomaticUpdates: database.AutomaticUpdatesNever,
|
||||
})
|
||||
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
OrganizationID: org.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
})
|
||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: ws.ID,
|
||||
TemplateVersionID: tv.ID,
|
||||
JobID: job.ID,
|
||||
InitiatorID: user.ID,
|
||||
})
|
||||
resource := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{
|
||||
JobID: job.ID,
|
||||
})
|
||||
|
||||
parentReadyAt := dbtime.Now()
|
||||
parentStartedAt := parentReadyAt.Add(-time.Second)
|
||||
_ = dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: resource.ID,
|
||||
StartedAt: sql.NullTime{Time: parentStartedAt, Valid: true},
|
||||
ReadyAt: sql.NullTime{Time: parentReadyAt, Valid: true},
|
||||
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
|
||||
})
|
||||
|
||||
row, err := db.GetWorkspaceBuildMetricsByResourceID(ctx, resource.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, row.AllAgentsReady)
|
||||
require.True(t, parentReadyAt.Equal(row.LastAgentReadyAt))
|
||||
require.Equal(t, "success", row.WorstStatus)
|
||||
})
|
||||
|
||||
t.Run("SubAgentExcluded", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
tmpl := dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: uuid.NullUUID{UUID: tmpl.ID, Valid: true},
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
ws := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: tmpl.ID,
|
||||
OwnerID: user.ID,
|
||||
AutomaticUpdates: database.AutomaticUpdatesNever,
|
||||
})
|
||||
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
OrganizationID: org.ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
})
|
||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: ws.ID,
|
||||
TemplateVersionID: tv.ID,
|
||||
JobID: job.ID,
|
||||
InitiatorID: user.ID,
|
||||
})
|
||||
resource := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{
|
||||
JobID: job.ID,
|
||||
})
|
||||
|
||||
parentReadyAt := dbtime.Now()
|
||||
parentStartedAt := parentReadyAt.Add(-time.Second)
|
||||
parentAgent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: resource.ID,
|
||||
StartedAt: sql.NullTime{Time: parentStartedAt, Valid: true},
|
||||
ReadyAt: sql.NullTime{Time: parentReadyAt, Valid: true},
|
||||
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
|
||||
})
|
||||
|
||||
// Sub-agent with ready_at 1 hour later should be excluded.
|
||||
subAgentReadyAt := parentReadyAt.Add(time.Hour)
|
||||
subAgentStartedAt := subAgentReadyAt.Add(-time.Second)
|
||||
_ = dbgen.WorkspaceSubAgent(t, db, parentAgent, database.WorkspaceAgent{
|
||||
StartedAt: sql.NullTime{Time: subAgentStartedAt, Valid: true},
|
||||
ReadyAt: sql.NullTime{Time: subAgentReadyAt, Valid: true},
|
||||
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
|
||||
})
|
||||
|
||||
row, err := db.GetWorkspaceBuildMetricsByResourceID(ctx, resource.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, row.AllAgentsReady)
|
||||
// LastAgentReadyAt should be the parent's, not the sub-agent's.
|
||||
require.True(t, parentReadyAt.Equal(row.LastAgentReadyAt))
|
||||
require.Equal(t, "success", row.WorstStatus)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21599,7 +21599,7 @@ JOIN workspaces w ON wb.workspace_id = w.id
|
||||
JOIN templates t ON w.template_id = t.id
|
||||
JOIN organizations o ON t.organization_id = o.id
|
||||
JOIN workspace_resources wr ON wr.job_id = wb.job_id
|
||||
JOIN workspace_agents wa ON wa.resource_id = wr.id
|
||||
JOIN workspace_agents wa ON wa.resource_id = wr.id AND wa.parent_id IS NULL
|
||||
WHERE wb.job_id = (SELECT job_id FROM workspace_resources WHERE workspace_resources.id = $1)
|
||||
GROUP BY wb.created_at, wb.transition, t.name, o.name, w.owner_id
|
||||
`
|
||||
|
||||
@@ -268,7 +268,7 @@ JOIN workspaces w ON wb.workspace_id = w.id
|
||||
JOIN templates t ON w.template_id = t.id
|
||||
JOIN organizations o ON t.organization_id = o.id
|
||||
JOIN workspace_resources wr ON wr.job_id = wb.job_id
|
||||
JOIN workspace_agents wa ON wa.resource_id = wr.id
|
||||
JOIN workspace_agents wa ON wa.resource_id = wr.id AND wa.parent_id IS NULL
|
||||
WHERE wb.job_id = (SELECT job_id FROM workspace_resources WHERE workspace_resources.id = $1)
|
||||
GROUP BY wb.created_at, wb.transition, t.name, o.name, w.owner_id;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user