mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
Filter query: has-agent connecting, connected, disconnected, timeout (#5145)
* WIP * has-agent:connecting, connected * Fix * Fix * has-agent:disconnected, timeout * Fix: typo * Fix * TODOs * databasefake * Fix: typo * More TODOs * databasefake * Timeout tests * Address PR comments * Implement FIXMEs * Renamings * Address PR comments * Fix: readability * Fix: refactor CASE logic * CASE logic * Fix * Use CTE * Polishing * Comment * WIP * IS NOT NULL * Without CTE * One more optimization * 2nd optimization
This commit is contained in:
@@ -871,6 +871,44 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
||||
}
|
||||
}
|
||||
|
||||
if arg.HasAgent != "" {
|
||||
build, err := q.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get latest build: %w", err)
|
||||
}
|
||||
|
||||
job, err := q.GetProvisionerJobByID(ctx, build.JobID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get provisioner job: %w", err)
|
||||
}
|
||||
|
||||
workspaceResources, err := q.GetWorkspaceResourcesByJobID(ctx, job.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get workspace resources: %w", err)
|
||||
}
|
||||
|
||||
var workspaceResourceIDs []uuid.UUID
|
||||
for _, wr := range workspaceResources {
|
||||
workspaceResourceIDs = append(workspaceResourceIDs, wr.ID)
|
||||
}
|
||||
|
||||
workspaceAgents, err := q.GetWorkspaceAgentsByResourceIDs(ctx, workspaceResourceIDs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get workspace agents: %w", err)
|
||||
}
|
||||
|
||||
var hasAgentMatched bool
|
||||
for _, wa := range workspaceAgents {
|
||||
if mapAgentStatus(wa, arg.AgentInactiveDisconnectTimeoutSeconds) == arg.HasAgent {
|
||||
hasAgentMatched = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasAgentMatched {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(arg.TemplateIds) > 0 {
|
||||
match := false
|
||||
for _, id := range arg.TemplateIds {
|
||||
@@ -909,6 +947,40 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
||||
return convertToWorkspaceRows(workspaces, int64(beforePageCount)), nil
|
||||
}
|
||||
|
||||
// mapAgentStatus determines the agent status based on different timestamps like created_at, last_connected_at, disconnected_at, etc.
|
||||
// The function must be in sync with: coderd/workspaceagents.go:convertWorkspaceAgent.
|
||||
func mapAgentStatus(dbAgent database.WorkspaceAgent, agentInactiveDisconnectTimeoutSeconds int64) string {
|
||||
var status string
|
||||
connectionTimeout := time.Duration(dbAgent.ConnectionTimeoutSeconds) * time.Second
|
||||
switch {
|
||||
case !dbAgent.FirstConnectedAt.Valid:
|
||||
switch {
|
||||
case connectionTimeout > 0 && database.Now().Sub(dbAgent.CreatedAt) > connectionTimeout:
|
||||
// If the agent took too long to connect the first time,
|
||||
// mark it as timed out.
|
||||
status = "timeout"
|
||||
default:
|
||||
// If the agent never connected, it's waiting for the compute
|
||||
// to start up.
|
||||
status = "connecting"
|
||||
}
|
||||
case dbAgent.DisconnectedAt.Time.After(dbAgent.LastConnectedAt.Time):
|
||||
// If we've disconnected after our last connection, we know the
|
||||
// agent is no longer connected.
|
||||
status = "disconnected"
|
||||
case database.Now().Sub(dbAgent.LastConnectedAt.Time) > time.Duration(agentInactiveDisconnectTimeoutSeconds)*time.Second:
|
||||
// The connection died without updating the last connected.
|
||||
status = "disconnected"
|
||||
case dbAgent.LastConnectedAt.Valid:
|
||||
// The agent should be assumed connected if it's under inactivity timeouts
|
||||
// and last connected at has been properly set.
|
||||
status = "connected"
|
||||
default:
|
||||
panic("unknown agent status: " + status)
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func convertToWorkspaceRows(workspaces []database.Workspace, count int64) []database.GetWorkspacesRow {
|
||||
rows := make([]database.GetWorkspacesRow, len(workspaces))
|
||||
for i, w := range workspaces {
|
||||
|
||||
@@ -132,6 +132,8 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
|
||||
arg.TemplateName,
|
||||
pq.Array(arg.TemplateIds),
|
||||
arg.Name,
|
||||
arg.HasAgent,
|
||||
arg.AgentInactiveDisconnectTimeoutSeconds,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
|
||||
@@ -6272,6 +6272,7 @@ FROM
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
workspace_builds.transition,
|
||||
provisioner_jobs.id AS provisioner_job_id,
|
||||
provisioner_jobs.started_at,
|
||||
provisioner_jobs.updated_at,
|
||||
provisioner_jobs.canceled_at,
|
||||
@@ -6373,7 +6374,7 @@ WHERE
|
||||
-- Use the organization filter to restrict to 1 org if needed.
|
||||
AND CASE
|
||||
WHEN $5 :: text != '' THEN
|
||||
template_id = ANY(SELECT id FROM templates WHERE lower(name) = lower($5) AND deleted = false)
|
||||
template_id = ANY(SELECT id FROM templates WHERE lower(name) = lower($5) AND deleted = false)
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by template_ids
|
||||
@@ -6388,29 +6389,68 @@ WHERE
|
||||
name ILIKE '%' || $7 || '%'
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by agent status
|
||||
-- has-agent: is only applicable for workspaces in "start" transition. Stopped and deleted workspaces don't have agents.
|
||||
AND CASE
|
||||
WHEN $8 :: text != '' THEN
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM
|
||||
workspace_resources
|
||||
JOIN
|
||||
workspace_agents
|
||||
ON
|
||||
workspace_agents.resource_id = workspace_resources.id
|
||||
WHERE
|
||||
workspace_resources.job_id = latest_build.provisioner_job_id AND
|
||||
latest_build.transition = 'start'::workspace_transition AND
|
||||
$8 = (
|
||||
CASE
|
||||
WHEN workspace_agents.first_connected_at IS NULL THEN
|
||||
CASE
|
||||
WHEN workspace_agents.connection_timeout_seconds > 0 AND NOW() - workspace_agents.created_at > workspace_agents.connection_timeout_seconds * INTERVAL '1 second' THEN
|
||||
'timeout'
|
||||
ELSE
|
||||
'connecting'
|
||||
END
|
||||
WHEN workspace_agents.disconnected_at > workspace_agents.last_connected_at THEN
|
||||
'disconnected'
|
||||
WHEN NOW() - workspace_agents.last_connected_at > INTERVAL '1 second' * $9 :: bigint THEN
|
||||
'disconnected'
|
||||
WHEN workspace_agents.last_connected_at IS NOT NULL THEN
|
||||
'connected'
|
||||
ELSE
|
||||
NULL
|
||||
END
|
||||
)
|
||||
) > 0
|
||||
ELSE true
|
||||
END
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces
|
||||
-- @authorize_filter
|
||||
ORDER BY
|
||||
last_used_at DESC
|
||||
last_used_at DESC
|
||||
LIMIT
|
||||
CASE
|
||||
WHEN $9 :: integer > 0 THEN
|
||||
$9
|
||||
END
|
||||
CASE
|
||||
WHEN $11 :: integer > 0 THEN
|
||||
$11
|
||||
END
|
||||
OFFSET
|
||||
$8
|
||||
$10
|
||||
`
|
||||
|
||||
type GetWorkspacesParams struct {
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
Status string `db:"status" json:"status"`
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
OwnerUsername string `db:"owner_username" json:"owner_username"`
|
||||
TemplateName string `db:"template_name" json:"template_name"`
|
||||
TemplateIds []uuid.UUID `db:"template_ids" json:"template_ids"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Offset int32 `db:"offset_" json:"offset_"`
|
||||
Limit int32 `db:"limit_" json:"limit_"`
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
Status string `db:"status" json:"status"`
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
OwnerUsername string `db:"owner_username" json:"owner_username"`
|
||||
TemplateName string `db:"template_name" json:"template_name"`
|
||||
TemplateIds []uuid.UUID `db:"template_ids" json:"template_ids"`
|
||||
Name string `db:"name" json:"name"`
|
||||
HasAgent string `db:"has_agent" json:"has_agent"`
|
||||
AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"`
|
||||
Offset int32 `db:"offset_" json:"offset_"`
|
||||
Limit int32 `db:"limit_" json:"limit_"`
|
||||
}
|
||||
|
||||
type GetWorkspacesRow struct {
|
||||
@@ -6437,6 +6477,8 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams)
|
||||
arg.TemplateName,
|
||||
pq.Array(arg.TemplateIds),
|
||||
arg.Name,
|
||||
arg.HasAgent,
|
||||
arg.AgentInactiveDisconnectTimeoutSeconds,
|
||||
arg.Offset,
|
||||
arg.Limit,
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ FROM
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
workspace_builds.transition,
|
||||
provisioner_jobs.id AS provisioner_job_id,
|
||||
provisioner_jobs.started_at,
|
||||
provisioner_jobs.updated_at,
|
||||
provisioner_jobs.canceled_at,
|
||||
@@ -146,7 +147,7 @@ WHERE
|
||||
-- Use the organization filter to restrict to 1 org if needed.
|
||||
AND CASE
|
||||
WHEN @template_name :: text != '' THEN
|
||||
template_id = ANY(SELECT id FROM templates WHERE lower(name) = lower(@template_name) AND deleted = false)
|
||||
template_id = ANY(SELECT id FROM templates WHERE lower(name) = lower(@template_name) AND deleted = false)
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by template_ids
|
||||
@@ -161,17 +162,54 @@ WHERE
|
||||
name ILIKE '%' || @name || '%'
|
||||
ELSE true
|
||||
END
|
||||
-- Filter by agent status
|
||||
-- has-agent: is only applicable for workspaces in "start" transition. Stopped and deleted workspaces don't have agents.
|
||||
AND CASE
|
||||
WHEN @has_agent :: text != '' THEN
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM
|
||||
workspace_resources
|
||||
JOIN
|
||||
workspace_agents
|
||||
ON
|
||||
workspace_agents.resource_id = workspace_resources.id
|
||||
WHERE
|
||||
workspace_resources.job_id = latest_build.provisioner_job_id AND
|
||||
latest_build.transition = 'start'::workspace_transition AND
|
||||
@has_agent = (
|
||||
CASE
|
||||
WHEN workspace_agents.first_connected_at IS NULL THEN
|
||||
CASE
|
||||
WHEN workspace_agents.connection_timeout_seconds > 0 AND NOW() - workspace_agents.created_at > workspace_agents.connection_timeout_seconds * INTERVAL '1 second' THEN
|
||||
'timeout'
|
||||
ELSE
|
||||
'connecting'
|
||||
END
|
||||
WHEN workspace_agents.disconnected_at > workspace_agents.last_connected_at THEN
|
||||
'disconnected'
|
||||
WHEN NOW() - workspace_agents.last_connected_at > INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint THEN
|
||||
'disconnected'
|
||||
WHEN workspace_agents.last_connected_at IS NOT NULL THEN
|
||||
'connected'
|
||||
ELSE
|
||||
NULL
|
||||
END
|
||||
)
|
||||
) > 0
|
||||
ELSE true
|
||||
END
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces
|
||||
-- @authorize_filter
|
||||
ORDER BY
|
||||
last_used_at DESC
|
||||
last_used_at DESC
|
||||
LIMIT
|
||||
CASE
|
||||
WHEN @limit_ :: integer > 0 THEN
|
||||
@limit_
|
||||
END
|
||||
CASE
|
||||
WHEN @limit_ :: integer > 0 THEN
|
||||
@limit_
|
||||
END
|
||||
OFFSET
|
||||
@offset_
|
||||
@offset_
|
||||
;
|
||||
|
||||
-- name: GetWorkspaceByOwnerIDAndName :one
|
||||
|
||||
Reference in New Issue
Block a user